728 x 90

Mapa ryzyka kodu

Mapa ryzyka kodu

W tym artykule uzupełniającym nasz poradnik dot. ransomware, chciałbym ujawnić jeden z bardziej innowacyjnych sposobów działania silników antywirusowych.

Zastanawiałeś się kiedyś, jak właściwie działa w środku program antywirusowy?

Ale nie, nie chodzi o szukanie znanych sygnatur wirusów. Owszem, ten kierunek działania silników antywirusowych też się cały czas rozwija, bo ilość sygnatur cały czas rośnie, dochodzi obsługa sygnatur wielowariantowych, dynamicznych fragentów, pól o zmiennej długości itp. – ale to temat na inny artykuł.

W tym artykule chciałbym przedstawić jeden z relatywnie nowszych pomysłów producentów takich programów, bo wypuszczony komercyjnie po raz pierwszy (wg wiedzy autora) w roku 2010. Jest nim właśnie mapa ryzyka kodu.

Mapa ryzyka kodu a testy jednostkowe

Tak, to nie pomyłka. Oryginalny pomysł pochodzi właśnie z „branży” testów jednostkowych:

  • pomiar złożoności cyklomatycznej poszczególnych funkcji w kodzie (zobacz więcej)
  • punktowanie różnych aspektów „lepszego” lub „gorszego” kodu
  • przedstawienie mapy pokrycia kodu testami w postaci graficznej – dokładnie takiej jak na powyższym obrazku (przedstawia on wynik działania biblioteki JaCoCo)

Jak widać, mapa ta przedstawia:

  • linie kodu, które są pokryte testami, tj. przez które zawsze przechodzi wykonanie kodu
  • linie pokryte warunkowo, a więc wykonanie tych linii zachodzi tylko w momencie spełnienia określonych już wewnątrz testowanego kodu warunków – a więc nie ma pewności, że zachowują się zawsze poprawnie
  • linie bez pokrycia, które w ramach testów w ogóle nie są uruchamiane

Ale o co chodzi?

Pomysł, jaki zrodził się w jednym z rosyjskich zespołów programistów, umożliwia znaczne przyspieszenie i pogłębienie procesu „emulacji” kodu, który dzisiaj jest głównym kierunkiem rozwoju silników antywirusowych.

Otóż jeśli kod wykonywalny jest fizycznie zbudowany z wielu następujących po sobie, wyodrębnialnych bloków (funkcji) wywołujących się wzajemnie:

aaaaaaa
bbbbbbb
ccccccc
ddddddd
eeeeeee
itd.

wówczas w procesie emulacji możemy wyliczać poziom (nie)szkodliwości każdego takiego bloku. I np. określić bloki bbbbbbb i ddddddd jako nieszkodliwe.

Idąc dalej, jeśli bloki te są ułożone przez kompilator fizycznie jeden po drugim (tak jak w kodzie źródłowym po przeprocesowaniu makr), wówczas można założyć, że blok ccccccc (leżący pomiędzy dwoma już zweryfikowanymi blokami) jest prawdopodobnie równie (nie)szkodliwy (albo przynajmniej nieco mniej podejrzany i przeznaczony do uproszczonej analizy).

Wykorzystując takie założenie, silnik antywirusowy oszczędza czas, co przekłada się na szybsze skanowanie, albo na możliwość przeznaczenia tego czasu na dokładniejsze „przyjrzenie się” innym fragmentom kodu.

Jak to wykorzystać?

Przede wszystkim, nie pisz kodu „ciurkiem” – refaktoryzuj tak bardzo, jak tylko się da. Przykładowo implementacja metod kryptograficznych nigdy nie powinna znaleźć się na tym samym poziomie, co obsługa plików lub innych miejsc do logowania komunikatów błędów. Im więcej drobnych funkcji, implementujących pojedyncze aktywności, tym lepiej.

Najlepsze na koniec

Twórcy co najmniej jednego silnika antywirusowego postanowili posunąć się dalej. Dużo dalej, identyfikując bowiem wspomniane wyżej bloki bbbbbbb itd. za pomocą sygnatur.

Wyobraź sobie, że masz do czynienia z jakąś znaną biblioteką, np. zlib. Jeśli jej jeszcze nie znasz, polecam przyjrzenie się jej budowie. Skupmy się tu na pliku deflate.c – zawiera on następujące funkcje:

slide_hash
deflateInit_
...
flush_pending
deflate
deflateEnd
deflateCopy
read_buf
lm_init
...
deflate_rle
deflate_huff

Jak widać w kodzie, funkcje te są relatywnie skomplikowane – co najmniej w porównaniu do kodu aplikacji „biznesowych”. Oczywiście również każdorazowa analiza ich skompilowanego kodu byłaby bardzo czasochłonna – i w gruncie rzeczy dość naiwna.

Kolejny pomysł był więc taki, aby użyć silnika szukającego znanych sygnatur do identyfikacji w pliku wykonywalnym skompilowanych fragmentów kodu przekładających się na poszczególne funkcje. Dodatkową korzyścią jest przy okazji możliwość identyfikacji starszych wersji kodu, zawierających znane błędy.

Kurtyna…

A co się stanie, jeśli gdzieś pomiędzy rozpoznane przez silnik funkcje np. deflate i deflateCopy wstawisz coś własnego?

A co, gdybyś przy każdej kompilacji losowo wstawiał poszczególne funkcje ze swojego kodu pomiędzy różne dobrze znane funkcje znanych bibliotek? Jak tego typu zmiany wpłyną na wykrywalność Twojego kodu przez silniki antywirusowe?


Intencją autorów ani wydawcy treści prezentowanych w magazynie PAYLOAD nie jest namawianie bądź zachęcanie do łamania prawa. Jeśli popełniłeś lub masz zamiar popełnić przestępstwo, bądź masz wątpliwości, czy Twoje działania nie będą łamać prawa, powinieneś skonsultować się z najbliższą jednostką Policji lub Prokuratury, a jeśli są one związane z pieniędzmi, dla pewności również z Urzędem Skarbowym.

Nie zezwala się na użycie treści prezentowanych w magazynie PAYLOAD, ani produktów dostępnych w sklepie PAYLOAD, do celów popełniania przestępstw lub przestępstw skarbowych.