Віруси. Віруси? Віруси! Частина 2



Як і обіцяв минулій частині, продовжимо розгляд вірусних движків. На цей раз мова піде про поліморфізм виконуваного коду. Поліморфізм для комп'ютерних вірусів означає, що кожен новий заражений файл містить в собі новий код вірусу-нащадка. Чисто теоретично, для антивіруса це повинно було б означати справжній кошмар. Якщо б існував вірус, який в кожному новому поколінні на 100% міняв би свій код, причому по справжньому випадковим чином, ніякої сигнатурний аналіз не зміг би його детектувати.

Можливо, десь є супер-програміст, який дійсно написав такий код, і саме тому ми про нього нічого не знаємо. Мені не дуже в це віриться, і навіть здається, що математики, які займаються математичним обґрунтуванням роботи обчислювальних систем, могли б довести, що не існує такого певного алгоритму поліморфізму, результат роботи якого не можна було б стовідсотково детектувати за допомогою іншого певного алгоритму. Але ми — люди прості, нас просто цікава ідея коду, який сам себе зраджує, а в світлі «алгоритм проти алгоритму», розгляд протистояння методів приховування виконуваного коду методів детектування для програміста повинно бути вельми цікавим.

Згадуємо і трохи доповнюємо першу статтюЗгадаю наших героїв з першої статті: вирмейкера і програміста антивірусної компанії і причеплений до них їх кармічних близнюків: розробника навісний захисту і крэкера. Перші намагаються приховати виконуваний код та інформацію про нього, другі — отримати доступ до характерного коду і його внутрішнім алгоритмами. У вірусної області превалюють автоматичні методи (вірусний движок і антивірусний детектор), в області захисту — ручні (параметри навісний захисту контролюються вручну, процес злому софта також є ручною роботою, незважаючи на велику кількість допоміжного софта).

За підсумками першої статті у нас є вірус, вміє коректно заражати виконуваний файл (тобто вміє при запуску файлу відпрацювати сам і коректно виконати код самого файлу), і антивірусний детектор, який знає, що вірус розміщується в суворо визначених місцях файлу, і що на деякій відстані від характерної точки (від точки входу, від початку секції, від кінця заголовка) існує фіксований набір байт, який характеризує даний вірус. Також, щоб стаття не з'їжджала в обговорення тим «що поганого робить вірус», давайте домовимося, що payload вірусу нічого не робить. Так можна відсіяти всі обговорення щодо характеру дій розглянутого коду і зосередитися на методах детектування і приховування.

Продовжуємо згадувати і доповнювати першу статтю. Схему протистояння вірусу і антивіруса можна за аналогією розглянути з точки зору злому комерційної програми. Замість інфектора тут працює власне програма, «навешивающая» захист виконуваний файл. Вона «псує» код самої програми, інформацію, необхідну для його відновлення ховає своє тіло, і таким же хитрим способом, як і вірус, приховуючи алгоритм своєї роботи, першим виконує код захисту, який перевіряє валідність серійного номера або час дії trial, і, потім, «полагодив» основну програму, передає їй управління. Крэкер в свою чергу виконує роль антивіруса, намагаючись дістатися до внутрішнього коду захисту і дізнатися його алгоритм. Виходить, що він займається тим же самим, що і авер, намагаючись знайти і зберегти характерну частину коду. Різниця лише в тому, що крэкер забере весь оригінальний код і виготовить з нього працездатний файл, а авер знайде в ньому характерний шматок і створить сигнатуру.

Найпопулярнішим способом зняти такий захист є dump способу програми в пам'яті на диск. Крэкер шукає момент, коли захист відпрацювала, розшифрувала і «полагодила» основну програму, і в пам'яті знаходиться працездатний образ коду цієї основної програми. Фактично він шукає можливість зупинити програму на так званій OEP (Original Entry Point) — «старої» точки входу захищається програми. У цей момент образ в пам'яті можна зберегти на диск. Він, звичайно, буде непрацездатний, але його можна полагодити, «перенастроює» Entry Point виконуваного файлу так, щоб він вказував на OEP, і, якщо програма в цей момент працездатна, такий спосіб буде працювати просто пропускаючи захист (там є ще багато маніпуляцій з відновленням зовнішніх викликів функцій, багаторазовим дампом на випадок, якщо програма розшифровується не повністю, і взагалі, це тема для десятка статей, але головний принцип саме такий). Іншим популярним способом є знайти шматок коду, який генерує серійний номер і, якщо це можливо, «викусити» його, і зробити маленький виконуваний файл, який генерує валідні серійні номери (keygen). Як ми побачимо нижче, схожий образ дій не чужий і антивірусному детектору.

Ще мені подобається проводити аналогії з біологічними системами, я постараюся сильно не обтяжувати вас цим. Вже дуже хочеться скоріше побачити штучний розум і життя.

Дізассемблер і відладчикРозуміння основних принципів їх роботи є важливим для розгляду області захисту виконуваного коду. Ви напевно про це щось знаєте, раз читаєте цю статтю, але все одно — або трохи потерпіть, або просто пропустіть цей розділ.

Дізассемблер приймає на вхід або виконуваний файл, або абстрактний буфер з кодом і, що досить важливо, перший адреса, з якого потрібно почати дизасемблювання. У разі виконуваного файлу це, наприклад, точка входу. Поставивши курсор на першу інструкцію, дізассемблер, визначає, що це за інструкція, її довжину в байтах, є вона інструкцією переходу, які використовує регістри, на які адреси в пам'яті посилається і т.п. Якщо інструкція не є інструкцією переходу, дізассемблер переходить до наступної інструкції, перемістивши покажчик вперед на довжину інструкції. Якщо це безумовний JMP або CALL, дізассемблер переміщує вказівник наступної інструкції туди, куди вказує адресу переходу. Якщо це умовний перехід (JZ, JNA тощо) то дізассемблер позначає наступними до розгляду відразу дві адреси — адреси наступної інструкції та адресу, на який можливий перехід. Якщо комбінація байтів не розпізнається, процес дизассемблирования даної гілки зупиняється. Також необхідно згадати про те, що дізассемблер зберігає інформацію про те, які інструкції посилаються на дану(!), що дозволяє визначати виклики функцій, і, найголовніше, хто їх викликає.

Дізассемблер перетворює послідовність байт в послідовність багатозв'язних структур, в яких зберігається інформація про кожному байті інструкції: є конкретний байт частиною опкода (коду операції), даними, адресою, на який звідки-то відбувається перехід і т.п. Кожна структура може містити посилання на одну або дві наступних структури і при цьому об'єктом, на який посилається довільне число інших структур (наприклад перша інструкція функції, яка викликається безліч разів). Також, розумні дізассемблер можуть стежити за покажчиком стека, або вміти розпізнавати і коректно позначати для дизассемблирования такі конструкції, як: mov eax, 0x20056789; call eax; Плюс розпізнавати характерні функції з набору інструкцій, встановлювати початкові точки для дизассемблирования вручну, коментувати окремі інструкції і зберігати результат дизассемблирования на диск, тому операція побудови графа викликів і розмітки структур досить затратна, ну а возитися з одним файлом можна днями. Але, як ми розглянули раніше, можлива ситуація, коли на диску у файлі перехід веде на зашифрований буфер, і дізассемблер в цьому випадку генерує кашу з інструкцій або зупиняється. В цьому випадку, треба роздобути цей зашифрований буфер прямо в runtime, коли він відкрито лежить в пам'яті, а для цього потрібно відладчик.

Основне завдання відладчика — зупинити програму в самому цікавому місці. Для цього використовується кілька способів. Можна відкрити пам'ять процесу, і замість однієї з інструкцій вписати int 3 — в цьому випадку процесор, виконуючи цю інструкцію згенерує виняток, а відладчик обробить його, відкриє своє вікно, відновить оригінальну інструкцію і покаже, що знаходиться в цій області пам'яті. Можна включити в процесорі прапор трасування, і тоді процесор буде генерувати це виняток на кожній інструкції. Нарешті, у процесора є налагодження регістри, можна поместиь в них деякий адресу, і процесор, отримавши доступ до пам'яті за цією адресою, зупиниться. Так що, наприклад, поставивши breakpoint на доступ до адреси початку зашифрованого буфера ми зупинимося перший раз, коли декриптор почне розшифрування і прочитає перший байт буфера, а другий раз, коли передасть туди управління. У цей момент вміст буфера можна записати на диск, нацькувати на нього дізассемблер і дізнатися всі його секрети. У просунутих захистах взагалі не існує такого моменту часу, коли в пам'яті лежить повний робочий код програми — частини коду розшифровуються шматками по мірі потреби. У цих випадках реверсеру доводиться збирати dump по шматках.

Захист від дослідження кодуТема захисту виконуваного коду від дослідження варта десятка статей, тому в рамках розглянутого питання зупинимося лише на кількох моментах. Статична захист коду від дослідження являє собою різні методи заплутування виконуваного коду і шифрування буферів з важливими ділянками коду з наступним расшифрованием в runtime. Заплутування коду можна реалізувати за допомогою спеціальних, обфусцирующих код компіляторів, а шифрування — за допомогою розглянутих нижче поліморфних движків (до яких з точки зору коду належать і комерційні захисту).

Динамічна захист передбачає, що програма може runtime визначити налагоджують її і вжити у зв'язку з цим деякі дії. Наприклад, прочитавши буфер з власним кодом програма може порівняти його контрольну суму з еталонною, і, якщо відладчик вставив код int 3 (див.вище), зрозуміти що її налагоджують або якимось іншим способом модифікували її код. Але, мабуть, найбільш надійний і переносне спосіб зрозуміти, що тебе налагоджують — це вимір часу виконання характерних ділянок коду. Сенс простий: вимірюється час (у секундах, папуг або тактах процесора) між інструкціями в деякому буфері, і, якщо воно більше деякого порогового значення — значить в середині програму зупиняли. Захист, зрозумівши, що її налагоджують, може, приміром, ігнорувати гілки, всередині яких може зупинитися реверсер і тупо не спрацьовувати, а вірус, видалити себе з системи. Для боротьби з такими ситуаціями реверсеры працюють в контрольованих середовищах, які можна легко відтворити, — наприклад, у віртуальних машинах, для яких можна відтворити все, аж до параметрів BIOS. Тому, досліджуючи код вірусу або захисту необхідно пам'ятати про те, що програма цілком може виявити факт дослідження і зробити що-небудь негарне.

Поліморфні движки або «код мельчал, движок міцнішав»Повернемося до вірусних движка. В певний момент розвитку DOS, після появи купи мега-актуальних на той момент пакувальників, програмісти, крім файлів, почали пакувати все, що пакується. А ".exe" файли займають купу місця, причому досить велика частина такого файлу — виконуваний код зі стабільним розподілом частот груп байт, який напевно добре тулиться правильним алгоритмом. Тому першими кроками до поліморфних движка стали пакувальники.

Принцип роботи пакувальника досить простий:
  1. беремо буфер з виконуваним кодом (кодову секцію, наприклад);
  2. упаковуємо її;
  3. беремо позиційно незалежний код распаковщика і доповнюємо його правильними адресами початку і кінця буфера з запакованим кодом;
  4. додаємо в кінець распаковщика перехід на OEP (першу інструкцію розпакованого коду);
  5. розміщуємо розпакувальник і буфер зі стисненим кодом у виконуваному файлі (правимо розміри секцій та/або EP).
Отриманий файл за розміром виходить набагато менше, ніж оригінальний. Після появи нових, крутих вінчестерів з об'ємом 100Мб це стало не так актуально, але, упаковка відкрила вирмейкерам і розробникам захистів багато нових можливостей:

  • розмір вірусу (незважаючи на наш круту вінчестер в 100Мб) все таки важливий. Якщо payload-код жирний і багатофункціональний, то весь вірус буде важче запхати в файл, особливо якщо використовується щось хитріше, ніж дописування в кінець файлу нової секції. Використання упаковки дозволить майже весь великий і складний код вірусу упакувати в буфер, який в рази менше оригінального розміру
  • буфер з упакованим кодом необов'язково розташовувати в секції з прапором виконання. Для просунутих инфекторов це дуже важливий фактор, адже основне тіло вірусу можна спокійно покласти куди завгодно. Після розпакування розпакувальник повинен подбати, щоб в області пам'яті, в яку був розпакований код, було дозволено виконання. Саме тому Windows API, що працюють з атрибутами доступу до пам'яті (всякі VirtualProtect, VirtualProtectEx, VirtualOuery і VirtualQuervEx) незмінно привертають увагу эвристиков
  • ну і на солодке, найважливіше — замість упаковки або після неї буфер з кодом можна зашифрувати, а ключ покласти в розпакувальник. Тепер це буде не розпакувальник, а декриптор. При кожному новому інфікуванні (або навішуванні захисту на виконуваний файл) буфер з кодом можна зашифрувати за допомогою нового ключа, і тоді буфер з кодом буде мати абсолютно новий зміст (зрозуміло при використанні хороших алгоритмів шифрування).:w надалі я не буду писати «упакований», але припускаю, що упаковка може бути включена в процес шифрування.
Ну ось він, власне, і є — перший поліморфний движок. Розпишемо детальніше приблизний алгоритм інфікування:
  1. Генеруємо новий ключ шифрування.
  2. Беремо код декриптора (де і як — поговоримо пізніше, у найпростішому випадку тупо дістаємо готовий код з нашого тіла).
  3. Впроваджуємо в нього (код декриптора) наш новий ключ шифрування.
  4. Впроваджуємо в код вірусу передачу управління з файлу-жертви і назад (поки код ще не зашифрований).
  5. Зашифровуємо новим ключем наш великий буфер з кодом.
  6. Нехитро укладаємо зашифрований буферу в файл-жертву (він суттєво відрізняється від попереднього, тому можна особливо не ховатися).
  7. Додаємо перехід на початок зашифрованого буфера в кінець декриптора.
  8. Хитро (наскільки це можливо) укладаємо декриптор в файл-жертву.
Подивимося, що вийшло: велика частина вірусу (зашифрований буфер) цілком змінюється від файлу до файлу, та незмінним залишається тільки невеликий декриптор. Цей декриптор фактично містить кілька адрес (від мінливих файлу до файлу, ключ розшифрування (також змінюється) та власне код расшифровщика. Антивірусу тепер довелося напружитися, характерні для даного вірусу патерни вкриті всередині зашифрованого буфера, і шматок коду для сигнатури тепер доводиться шукати в декрипторе, а він невеликий і містить в собі набагато менше характерних ділянок коду і даних.

Таке спрощення завдання послужило причиною появи більш просунутих поліморфних движків, які при зараженні змінюють тільки код декриптора — адже розібратися з невеликим шматком коду набагато простіше, ніж з усім payload-кодом. Радісні вирмейкеры і розробники захистів потирають руки і вивчають способи сховати маленький декриптор хитріше, а віри і крэкеры лагодять дізассемблер, у яких після спроб дизасемблювати рандомные рядка байтів, на які в коді присутня JMP, їде дах.

Еволюція вірусних движківТепер авер витрачає трохи більше часу на створення сигнатур, тому працювати доводиться з невеликим об'ємом коду, в якому менше характерних ділянок. А вирмейкер стурбований лише мутацією досить невеликого декриптора з досить простим внутрішнім алгоритмом, і задача приховати його від детектора тепер здається більш реальною. Враховуючи, що антивірус порівнює сигнатуру за фіксованим зсувом, спочатку вирмейкер намагається різними способами зрушувати код декриптора і, відповідно, дискредитувати характерну сигнатуру всередині нього.

NOP зони або «авось пронесе»Першим, простим прийомом, який приїхав до віруси з эксплоитинга вразливостей, є NOP-зони. Коли атакуючому вдалося успішно провести експлойт якої-небудь проблеми, і змусити процесор здійснити перехід по заданому адресою, але при цьому невідомий точний адреса розташування shellcode в пам'яті, атакуючий може зробити так: купу простору перед власне кодом експлойта заповнити NOP-ами:

addr1: nop
nop
;... ще дуже багато NOP-ів 
nop

addr2: jmp addr3; shellcode
pop esi; shellcode
xor edx,edx shellcode
;... 

Тепер можна зробити перехід «кудись туди», NOP-зону. Якщо відомо тільки приблизне розташування в пам'яті, цей прийом дозволяє успішно виконати shellcode.

Так само просто можна вчинити і з декриптором, просто при інфікуванні поміщаємо його в різні місця довгою NOP-рядка. А подекуди (там, де це не разломает переходи) можна напхати цих NOP-ів прямо в код. В цьому випадку все буде коректно працювати, але зсуву характерної сигнатури завжди будуть різні. Зрозуміло зсуву для інструкцій переходів доведеться перераховувати.
Занадто халявне рішення не сильно напружило віри, який просто додав в базу ознака «пропусти все NOP-и при підрахунку сигнатури», але цей маленький крок вельми примітний тим, що вперше детектор почав розглядати інструкції, а не байти. Але про це пізніше.

Пермутация або «склади як небудь»Розмірковуючи, як би дискредитувати порівняння по сигнатурі, не розламавши код декриптора, вирмейкер приходить до ідеї пермутации. Пермутация — це перестановка блоків коду в кожному новому поколінні. Код складається з деякої кількості блоків, ці блоки переставляються місцями в кожному новому поколінні вірусу, і зв'язуються JMP-ами. Як завжди, на папері все просто, а проблеми починаються в реалізації. Всередині блоків є умовні та безумовні переходи і виклики функцій, тому такі логічні блоки повинні залишатися цілими. При цьому, чим товще блоки, тим менше варіативність отриманого декриптора, а чим менше розмір блоку, тим більше доводиться додавати переходів, роздуваючи код декриптора, і тим складніше зберегти цілісність. Для вирівнювання блоків по довжині можна, наприклад, використовувати NOP-зони.

Ось приклад алгоритму: в тілі вірусу зберігаємо вже готовий набір з блоків з розміткою (яка являє собою номер блоку і його довжину). Потім беремо рендомний блок, записуємо його в буфер, і правимо JMP в кінці попереднього блоку. Доповнюємо результат JMP-му на перший блок і буфер з рандомно переставленными блоками готовий. На відміну від попередніх ігор, це вже досить серйозна серйозна заявка, кожне нове покоління, нехай і за рахунок безумовних переходів, але все таки породжує, з точки зору зсувів, цілком інший код. Вирмейкер з задоволеною посмішкою засинає.
[block 1] [block 2] [block 3] [...] [block N]
[jmp block 1] [block 2]
[jmp block 3]
[block 1]
[jmp block2]
[block 3]
[jmp block 4]
[...] [block 4]
[jmp block 5]
Прокидається авер. Трассируя код кількох поколінь вірусу, він розуміє, що в декрипторе має справу з перестановкою блоків, і треба допрацьовувати детектор, по можливості не позбавивши його продуктивності. Він вирішує написати швидкий автоматичний дізассемблер, який вміє бігти за інструкціями, зупинятися тільки на операторах переходу, обчислювати адреса переходу і переходити до аналізу інструкцій, що знаходяться за адресою переходу.

Тепер в антивірусній базі лежить наступна вказівка: починаючи з точки входу крокуй за інструкціями, здійснюючи переходи відповідно встреченным JMP-ам, і, пройшовши N інструкцій, порівняй сигнатуру. Якщо сигнатура в десятому блоці, доведеться пройти до десятого переходу, якщо всередині можливі умовні переходи (JZ), то їх умовно можна вважати двома переходами — на наступну інструкцію і на адресу переходу, і, відповідно, разветвлять прохід по інструкцій. Зрозуміло, ніхто не скасовував і детектування простіше, наприклад якщо блоки у вірусу фіксованої довжини L і їх N штук, можна просто провести N порівнянь по сигнатурі за зміщень [0, (1 * L), (2 * L), ..., ((N-1) * L)].

Оцінимо трудомісткість процесу пошуку з використанням дизассемблера. Дізассемблер мінімально повинен забезпечувати визначення довжини інструкції та перетворення VA (Vitual Address) to RVA (Relative Virtual Address) (адресу, вказану в JMP в зсув у файлі). Визначення довжини інструкції — це в принципі досить швидкий алгоритм (звернення до елемента масиву і обчислення наступного кроку на основі прапорів, записаних в соотв. елемент масиву), а перетворення адреси це пара елементарних операцій додавання адрес, на основі інформації про те, якою секції належить адресу. Плюс трохи розуму для визначення дешевих трюків для заміни банального JMP next_block_address, таких, наприклад, як:

XOR eax,eax; 
JZ next_block_address;

; або
PUSH next_block_address;
RET;

; або
MOV eax, next_block_address;
CALL eax;

Це не сильно страшні в плані продуктивності алгоритми, але, тим не менш, на обчислення CRC32 від короткого рядка за заданим зсуву це вже зовсім не схоже, і сердитий тестувальник лається на те, що детектор вже опівночі жує тестову базу і зжер весь процесор.

Як це водиться, якщо що-то, що включили, а воно гальмує, треба або оптимізувати це, або постаратися не включати це без потреби. Перший спосіб, на жаль, не котить — в простому дізассемблер багато не наоптимизируешь, тому авер переходить до улюбленого місця всіх антивірусів — евристичному аналізатору.

Евристичний аналізатор або «розтин покаже»У першій статті ми вже торкнулися евристичного аналізу — дійсно, існують ознаки, які з різним ступенем вірогідності можуть говорити, що файл був инжектирован код. І тоді, авер дійсно виділив деякі з них, які були підозрілими, але ніяк не тягнули на право заявляти про 100% факт зараженості файлу. Тоді він їх просто закомментировал, тому що витратив на них чимало часу і видаляти їх зовсім було шкода. Тепер на їх основі можна прийняти рішення — запускати більш важкий, використовує дизасемблювання, аналіз файлу, чи ні.

Є і ще одна проблема — тому евристик реагує на все підозріле, комерційні захисту викликають у нього непідробний інтерес, тому віру довелося завести в базі сигнатур ще пару сотень «білих» під популярні навісні захисту — їх чіпати не можна. Саме завдяки їм ми все таки можемо нормально запускати різні комерційні софтина. А при написанні власного софта, що використовує методи роботи з виконуваним кодом, непогано б перед релізом прогнати всі файли своєї програми на всіх популярних антивірусах де небудь на virustotal. За непопулярні можна сильно не хвилюватися, евристичний аналізатор важко потягнути так само просто, як бази сигнатур і навряд чи аналізатор малопопулярного антивіруса буде так же крутий, як той, який розроблявся довгі роки.
Варто, звичайно згадати і про спроби вирмейкера замаскувати свій вірус під популярну захист. Для цього, потрібна власне сигнатура, і він починає розбирати антивірусну базу, щоб зрозуміти, куди б покласти потрібні байти, щоб антивірус прийняв його вірус за захист. Та й взагалі, виготовляючи наступну версію вірусу, непогано б ознайомитися з кодом, який детектує поточну. Так що антивірусні бази також є об'єктами реверс-інжинірингу, а код детектора також піддається аналізу зі сторони вирмейкеров.
Але повернемося до нашого евристичному аналізатору, наведемо кілька евристичних ознак:

  • Точка входу в секції відкритою для запису(rwx). Відкрита для запису, виконувана секція, яку відразу передається управління, з великою ймовірністю свідчить про наявність самомодифицирующегося коду, такі секції використовуються в переважній більшості випадків вірусами і програмними захистами.
  • Інструкція переходу в точці входу. Особливого сенсу в розміщенні інструкції переходу в точці входу немає і такий ознака вказує на наявність самомодифицирующегося коду у файлі.
  • Точка входу в другій половині секції. Віруси, що використовують розширення секції, в більшості випадків розташовуються в кінці секції. Це нетипово для нормальних файлів, тому така ситуація є підозрілою.
  • Поломки в заголовку. Деякі модифікації заголовка після інфікування залишають файл працездатним, але сам заголовок при цьому містить помилки, які лінкер б не допустив. Це теж підозріло.
  • Нестандартний формат деяких службових секцій. У виконуваних файлах є службові секції, такі як, наприклад, .ctors, .dtors, .fini і т.п. Особливості цих секцій можуть використовуватися для зараження вірусами файлу. Порушення формату такої секції також є підозрілим.
  • … і ще сотня таких ознак

Таких ознак може бути безліч, вони мають різну ступінь небезпеки, деякі можуть бути небезпечними лише в комбінації з іншими, але це найпотужніший інструмент для прийняття рішення про необхідність більш ретельного аналізу і про факт зараження. Обійти евристик вельми непросто (я маю на увазі зробити так, щоб він не видав навіть попередження). Це або всякі платформозависимые рішення, які використовують особливості певних компіляторів або фреймворків (типу перезапису стандартних конструкторів або деструкторів), які досить швидко потрапляють у евристичну базу, або використання реально великих і складних инфекторов, вміють дійсно якісно розташувати код у файлі.

Коли евристичні ознаки говорять, що «файл 100% заражений», але важкий аналіз нічого не знайшов, антивірус пише, що файл заражений вірусом з назвою типу: «Generic Win32.Virus», або по-нашому «Якийсь Вірус Win32». Такі повідомлення часто можна зустріти на всяких кейгенах, лоадерах і т.п. У минулій статті я вже говорив, що саме з цієї причини в інструкціях з установки піратського софту пишуть «перед установкою відключіть антивірус». Також я ще раз хочу звернути увагу на один з найважливіших інформаційних активів антивірусних компаній — колекцію виконуваних файлів достатнього обсягу, щоб на ній можна було б тестувати аналізатор, не боячись випустити у світ версію, яка буде кидатися на легітимні файли, які туди обов'язково додаються. Ображені кейгены і лоадери напевно обурюються, що їх туди не додають оперативно, але хто ж їх, вирусню, слухає…

Отже, попрацювавши над эвристиком авер приходить до наступного загальним алгоритмом детектування:

  1. Перевірити файл звичайним сигнатурным пошуком.
  2. Якщо успішно — вважати файл зараженим.
  3. Якщо знайдена «біла» захист — вийти мовчки.
  4. Перевірити файл евристичним аналізатором.
  5. Якщо не знайдено жодної ознаки — вийти.
  6. Якщо знайдені ознаки достатньої ваги, запустити аналіз, використовує дизасемблювання.
При цьому, якщо евристичні ознаки досить серйозні, щоб говорити про зараження, вважати файл зараженим незалежно від того, знайшов аналізатор що-небудь, чи ні.

Роботи зроблено багато, і антивірус тепер, нехай і не ідентифікуючи загрозу, але з дуже високим відсотком достовірності розпізнає факти інфікування. Підтримка тестової бази виконуваних файлів дозволяє без побоювання додавати нові евристичні ознаки, як тільки з'являються нові алгоритми інфікування, і, нарешті, антивірус уміє реагувати на загрози до того, як нова зараза встигає поширитися. Треба відзначити, що якщо раніше тестувати антивірус на всіх виконуваних файлах у світі здавалося абсолютно нереальним, то наразі база всіх можливих в WWW виконуваних файлів вже не здається фантастикою. Виконуваний файл штука, требущая серйозних тимчасових витрат, і світ виготовляє їх не так вже й багато. Крім того тестування на цій величезній базі файлів легко распараллеливается, тому цілком реально дресирувати евристик на величезних масивах можливих даних. Щасливий авер п'є своє какао і лягає спати…

«Мутанти наступають». МетаморфізмВирмейкер на цей раз вирішує не проводити маніпуляції над вже існуючими кодом, а генерувати новий код декриптора в кожному новому поколінні. Це і є метаморфізм — генерація нового коду в кожному новому поколінні. На відміну від пермутации, в даному випадку код не просто переставляє блоки всередині себе, а реально змінює свій зміст. У теорії це повинно означати беззастережну перемогу вирмейкера над точним детектуванням його вірусу (евристику-то ніхто не відміняв). Тепер, сигнатура, зроблена для одного покоління вірусу стане неактуальною для іншого, а, навіть якщо і продовжить детектувати вірус, то не дасть гарантії працездатності в наступному поколінні.

Що ж являє собою метаморфный генератор? Основою для генерації нового покоління декриптора є якийсь «базовий код», причому якою мовою він написаний — несуттєво. Він зберігається всередині зашифрованого тіла вірусу, тому може бути постійним. Там же, в тілі вірусу, лежить движок, який на основі кожної інструкції цього «базового» коду кожен раз генерує новий, виконуваний код. Це дуже нагадує компілятор — на вході деякі семантичні конструкції, на виході готовий до виконання процесором код. Ще подібна генерація коду, що виконується на основі базового коду відбувається у віртуальних машинах — у момент, коли на певній платформі віртуальна машина виконує підготовлений байт-код. Саме в цей момент «базовий» байт-код перетворюється в конкретний виконуваний, який розуміє даний процесор. І, якщо кожну нову платформу вважати новим поколінням коду, то сукупність віртуальних машин під різні платформи є метаморфным генератором.

Якщо згадати, що ми генеруємо код декриптора, який максимально незалежний від того, де і коли він виконується (не містить системні виклики, не звертається до збереженого стану, не містить складні об'єкти), і працює з вже готовими даними в пам'яті за відомим зміщень, то завдання здається цілком вирішуваною. На вході у генератора три основних параметри — адреса зашифрованого буфера, його довжина і ключ. Ну, нехай буде ще seed для псевдовипадковою генерації всяких констант, майбутніх ключів і т.п. Також декриптор містить умовні переходи, але тільки в межах свого тіла, що також трохи спрощує завдання.

Генерація сміттяВирмейкер вирішує підійти до питання, використовуючи генерацію безлічі зайвих інструкцій, і «розмішувати» істинний код декриптора в них. Нехай навіть вихідні інструкції залишаться незмінними, в купі інших інструкцій вичленити необхідні для порівняння по сигнатурі буде дуже проблематично. Незважаючи на непоказну назва, генератор сміття є найбільш складною і цікавою частиною метаморфного движка, адже сміття або не сміття, а згенерувати треба виконуваний код, який не буде ламатися сам і не буде псувати код основний декриптора. В процесі «замішування» необхідно буде:

— стежити за переміщеннями характерних точок (адрес переходів, виходів з циклу тощо);
— стежити, щоб сміттєвий код не зіпсував необхідні регістри і регістр прапорів.

Дуже привабливими кандидатами на звання сміттєвих інструкцій є всякі MMX, SSE, floating-point інструкції, їх можна легко згенерувати скільки треба, головне — не чіпати стек, не писати в регістри загального призначення і не ламати прапори, необхідні декриптору, і перший метаморфный код виглядає ось так:

mov ecx, 100h; ; декриптор
lbl0: mov eax, [esi + ecx] ; декриптор
xor eax, edi ; декриптор 
mov [ebx], eax ; декриптор
add ebx, 4h ; декриптор

movd mm0,edx ; сміття
movd mm1,eax ; сміття
psubw mm1,mm0 ; сміття

lbl1: jcxz lbl2 ; декриптор, вихід з циклу

psubw mm1,mm0 ; сміття
movd mm3,ecx ; сміття

jmp lbl0 ; декриптор, продовження циклу
lbl2: sub ebx, 100h ; декриптор

Авер не сильно хвилюється, тому евристик все-таки продовжує лаятися на заражені файли (працюючи над генератором, вирмейкеру хочеться возитися з серйозним инфектором), але точно ідентифікувати конкретний вірус вже не може. Тому темної ночі віру сниться інфектор, який не піддається евристику, і його нав'язливою ідеєю стає необхідність задетектить гада зі 100% точністю. Щоб точно ідентифікувати вірус, детектор треба допрацьовувати — тепер необхідно, почавши з точки входу, крокувати по інструкцій, пропускати всі сміттєві і додавати в аналізовані тільки значущі, а це означає, що дізассемблер в детекторі починає рости. Якщо ви пам'ятаєте про NOP зони в абзаці про пермутацию, то пропуск NOP-ів при набиванні буфера для порівняння по сигнатурі, фактично, і є перший підхід до сняряду — детектор пропускає NOP-и, як сміттєві інструкції. Тепер авер замість порівняння з 0x90 (опкод NOP) використовує дізассемблер (чим швидше, тим краще), що:

  1. Переміщує вказівник на початок наступної інструкції (дізассемблер довжин).
  2. Говорить, чи є дана інструкція сміттєвої (NOP, MMX, SSE тощо).
  3. Значущі інструкції додає в аналізований буфер.
  4. У разі безумовного переходу позначає адреса переходу, як наступний аналізований.
  5. У разі умовного переходу позначає обидві можливих гілки коду для подальшого аналізу.
Таким чином авер збирає буфер з інструкцій, які складають основний код декриптора, і вже в ньому може провести порівняння по сигнатурі. Це поки ще досить швидка процедура, але, програмуючи її, авер все більше хвилюється: «завжди я зможу відрізнити сміттєву інструкцію від значущою?» Вирмейкер, відчуваючи це, допрацьовує свій генератор сміття. Тепер він кличе на допомогу інструкції збереження контексту: pushad/popad (покласти або дістати зі стека всі регістри загального призначення) і pushfd/popfd (те ж саме для регістра прапорів).

<pre>
mov ecx, 100h; ; декриптор
lbl0: mov eax, [esi + ecx] ; декриптор
xor eax, edi ; декриптор 
mov [ebx], eax ; декриптор
add ebx, 4h ; декриптор

pushad ; зберігаємо регістри
pushfd ; зберігаємо прапори
mov eax, 12321h ; сміття

xor edx,edx ; робимо що хочемо
sub eax, esi ; продовжуємо смітити
popfd ; відновлюємо прапори
popad ; відновлюємо регістри

lbl1: jcxz lbl2 ; декриптор, вихід з циклу

pushad ; зберігаємо регістри
pushfd ; зберігаємо прапори
shr ebx, 4 ; сміття
popfd ; відновлюємо прапори
popad ; відновлюємо регістри

jmp lbl0 ; декриптор, продовження циклу
lbl2: sub ebx, 100h ; декриптор
</pre>


Тепер дізассемблер аналізатора повинен стежити не тільки за тим які інструкції він аналізує, але і знаходяться вони в області «робимо що хочемо». А це означає, що у дизассемблера з'являються глобальні змінні, що зберігають інформацію про те, в якому місці програми ми знаходимося. Все стає ще цікавіше. Ну а взагалі, інструкції збереження контексту для будь-якого реверс-інженера як червона ганчірка для бика — при аналізі виконуваних файлів будь-яка зустріч з такою інструкцією означає «швидше став сюди breakpoint!».

Наступною ітерацією у розвитку метаморфного коду є генерація необхідного дії різними способами за допомогою різних арифметичних операцій і всяких ассемблерних хитрощів. Типу того:
«базова інструкція» згенерований код 1 згенерований код 2
virt_mov eax, 10h mov eax, 20h;
sub eax, 10h;
mov edx, 10h;
mov eax, edx;
virt_mov ecx, 08h xor ecx,ecx;
add ecx, 08h;;
mov ecx, 04h;
add ecx, 04h;
virt_sub eax, ecx neg ecx;
add eax, ecx;
mov edx, ecx;
sub eax, edx;
Наприклад, так можна працювати з усіма константами: припустимо в «вихідному коді» лежать дві інструкції «virt_mov edx, 10h» і «virt_mov ecx, 100h». Тоді генеруючи новий код, движок вибирає випадкову константу, наприклад, «50h», і використовує її для роботи зі всіма абсолютними значеннями, і «virt_mov edx, 10h» мутує в «mov edx, 50h; sub edx, 40h;», a «virt_mov ecx, 100h» в «mov ecx, 50h; add ecx, B0h». Різні константи породжують різні байт-патерни, що змушує віра додавати в дізассемблер все більше логіки, реалізовувати wildcards в сигнатурах за інструкціями, роблячи для інструкцій щось на зразок «mov eax, <wildcard-константа>; <skip сміття>; mov ecx, <wildcard-константа>». Це вже не дуже просто, і вже не дуже швидко, і взагалі пахне смаженим…

Після аналізу коду детектора, крім констант, для модифікації даних в інструкціях, вирмейкер тепер хоче міняти і весь набір регістрів, використовуваний в декрипторе. Щоб дозволити собі таке, необхідно використовувати поділ регістрів — частина регістрів є робочими для декриптора, а решта — для генератора сміття. У цьому випадку генератор сміття не чіпає робочі регістри, а також не псує регістр прапорів. Наприклад, весь декриптор може працювати тільки з eax, edx і esi. Тоді всі породжені генератором інструкції повинні працювати тільки з ebx,ecx, edi і не міняти прапори. При цьому, набір регістрів повинен змінюватися в кожному новому поколінні вірусу.

. . . 
mov eax, 10h ; декриптор

mov ebx, 20h ; декриптор, ebx - сміттєвий регістр, його можна зіпсувати командою xchg
xchg edx, ebx ; для того, щоб завантажити 20h в регістр edx 

xor ecx,ecx ; сміття
inc ebx ; сміття
add ecx,ebx ; сміття
add eax, edx ; декриптор
mov edx, [esi] ; декриптор
xchg edi,ebx ; сміття
cmp edx, 0 ; декриптор
. . . ;

Генератор сміття, загалом-то, може змінювати і «заборонені» регістри і прапори, але при цьому повертати їх стан назад. У цьому випадку «справжні» інструкції декриптора можна впроваджувати не в будь-яке місце буфера зі сміттям, а тільки в ті місця, де значення в регістрах і прапори «чисті».

По-справжньому серйозним випробуванням стає виготовлення сигнатури за інструкціями, якщо використовуються різні асемблерні хитрощі, що дозволяють реалізувати необхідні дії за допомогою абсолютно несхожих патернів, наприклад:
«базова інструкція» згенерований код 1 згенерований код 2
virt_push eax sub esp, 04h;
mov [esp], eax;
mov edx, esp;
sub edx, 04h;
mov [edx], eax;
virt_mov eax, ebx lea eax,[ebx]; push ebx;
xchng eax,ebx;
pop ebx;
Таких патернів — величезна кількість, при бажанні ви легко знайдете. Тепер генератор стає набагато складніше, оскільки сильно усложнаяется робота зі зміщеннями, стеком, прапорами і т.п. Але це ще один серйозний крок до ідеального генератора.
Отже, для породження коду, в якому детектору буде максимально складно стверджувати, чи є поточна інструкція сміттєвої, необхідний генератор, здатний генерувати код з наступними властивостями:
  • містити ходові цілочисельні інструкції з регулярними регістрами;
  • не використовувати інструкції збереження-відновлення контексту для сміттєвого відділення коду від істинного;
  • набір регістрів як в сміттєвих блоках, так і в інструкціях декриптора в кожному поколінні повинен бути різним;
  • базові інструкції декриптора повинні перетворюватися в блоки інструкцій різної довжини;
  • байт-структура мутованих інструкцій повинна бути максимально варіативної.
«Наша відповідь Чемберлену». ЕмуляціяПрипустимо, що вирмейкер через 42 місяці роботи написав-таки майже ідеальний метаморфный генератор, і детектор не може фільтрувати інструкції за принципом «сміття-не сміття» і, відповідно, не може зібрати достатньо даних для порівняння по сигнатурі. Але і віра в запасі є відповідь, настільки ж складний у реалізації, але здатний впоратися з детектуванням конкретного вірусу використовує навіть настільки просунуті методи метаморфізму. У процесі протистояння новим генераторам і виготовлення все більш складних дизассемблерных сигнатур дизассемблирующий движок дійшов до стану, коли крім поточної інструкції він зберігає також і все оточення даної конкретної інструкції, стежачи за змінами в регістрах, прапорах, покажчиком стека і т.п. Критично поглянувши на отриманий код авер раптом розуміє, що, фактично, запрограмував софтовую модель процесора. Проходячи по інструкціям його код оновлює змінні, відповідні регістрів, стежить за прапорами, щоб передбачити умовний перехід, відстежує верхівку стека тощо, тобто фактично виконує читається код віртуально. Метод детектування, що використовує емулює виконання движок так і називається — емуляція.

Згадаймо, як крекери знімають навісну захист виконуваних файлів, зупинивши її на першої інструкції в момент, коли працездатний розпакований код лежить в пам'яті (Original Entry Point). Для розуміння того, як емулятор може допомогти детектору дістатися до смачного, зашифрованого payload коду я коротко опишу один простий, але дієвий спосіб зупинити програму на OEP. Заснований він на тому, що в момент старту основної програми покажчик стека повинен бути встановлений в своє початкове значення, тому що на стеку лежать дані, що відносяться до оточення програми, агрументы, змінні середовища і т.п. Тому, можна бути впевненим, що після того, як захист отрботает, esp повернеться до того значення, яке було встановлено на початку. Крекер зупиняє програму прямо в точці входу, запам'ятовує значення покажчика стека esp і ставить умовний breakpoint, який зупинить програму в той момент, коли esp стане рівним тому самому значенню, яке було зафіксовано на момент старту. З великою ймовірністю саме в цей момент він буде перебувати на OEP (ну або в корені декриптора з точки зору вкладеності функцій). Декриптор вірусу (якщо він використовує стек, звичайно) також повинен повернути покажчик стека на місце, і код детектора, біжить за інструкціями, може стежити за цим покажчиком, завівши змінну cur_esp і змінюючи її кожен раз, коли зустрічає інструкції, змінюють esp.

. . . ; base_esp = cur_esp; 
push eax ; cur_esp -= 4;
mov eax, 1h ; -
push edx ; cur_esp -= 4;
. . . ; -
pop edx ; cur_esp += 4;
pop eax ; cur_esp += 4; (cur_esp == base_esp) !!!
. . . ; тут можливо відпрацював декриптор або весь вірус

Як раз в цей момент, коли стек відновлений, в пам'яті знаходиться расшифрованое тіло вірусу, а в ньому смачні дані для постійної сигнатури. У випадку з вірусом навіть необов'язково чекати всій розпакування, напевно всередині декриптора все таки є постійні дані типу довжини ключа, довжини блоку, зміщення (яке залежить від расмещения секція у файлі). Іншими словами, якщо йти за декриптору інструкція за інструкцією, то яким би не був породжений метаморфным генератором код, обов'язково наступить момент, коли де-то в пам'яті або в регістрах будуть лежати характерні для саме цього вірусу дані. Піймавши цей момент, можна визначити що це за вірус. Також йдучи таким чином за інструкціями можна очікувати небезпечні дії — виклики підозрілих API, запис в підозрілі файли тощо

Залишилася дрібничка — автоматизувати цей процес. Як я вже згадував, емулятор — це софтова модель процесора, виконуючого код нашого файлу. Причому процесора-халявщика, тому що йому не треба писати в пам'ять, робити введення-виведення і взагалі, йому цікаво тільки те, що дозволить зупинити програму в потрібному місці. Він не вміє MMX, SSE і взагалі, чим менше він вміє (при цьому виконуючи свою функцію), тим краще (т.к. халявщик він умовний, і вельми тяжеловесен). Припустимо, що в якийсь момент декриптор кладе на стек рядок «BANANAS», знаючи це авер може виконувати код на віртуальному процесорі постійно перевіряючи верхівку стека на наявність цього рядка.
Движок емулятора має в собі змінні, відповідні регістрів, прапорів, пам'ять під емуляцію стека і т.п. Я навмисно залишив блоки між pushad/popad, щоб продемонструвати, що емулятор може пропускати в тому числі і блоки коду, а не окремі інструкції, тому емуляція — процедура не з простих. Ось як воно працює приблизно (нехай за адресою в ESI лежить цей самий «BANANAS\0»).

mov ecx, 0h; ; ecx_var = 0;
lbl0: mov eax, [esi + ecx] ; esi і eax ми знаємо (з минулого емуляції),
; тому завантажимо з правильного місця в пам'яті
; покажчик на "BANANAS" 
xor eax, edi ; eax_var = eax_var XOR edi_var; 
push eax ; esp_var -= 4; *esp_var = eax_var; 

pushad ; включаємо режим неробства
pushfd ; skip
mov eax, 12321h ; skip


xor edx,edx ; skip
sub eax, esi ; skip
popfd ; skip
popad ; вимикаємо режим неробства

; "якісні" сміттєві інструкції
; емулятор не знає про це
; і змушений виконувати їх віртуально
mov edx, 23h ; edx_var = 23h; 
or edx, eax; ; edx_var = edx_var OR eax_var; 

lbl1: inc ecx ; ecx_var++;
cmp ecx, 8h; ; if (ecx_var == 8) { goto lbl2; }:

pushad ; включаємо режим неробства
pushfd ; skip
shr ebx, 4 ; skip
popfd ; skip
popad ; вимикаємо режим неробства

jmp lbl0 ; goto lbl0
lbl2: sub ebx, 100h ; на стеку лежить "BANANAS" - попався!

Зрозуміло, емуляція вимагає обробки безлічі специфічних ситуацій, ніяких ресурсів не вистачить, щоб чесно емулювати кожну інструкцію, і при цьому не забувати виконувати необхідні перевірки оточення. Тому емулятори детектують цикли, передають частини коду на виконання реального процесора, загалом там, як і в метаморфных генераторах, простору для творчості хоч відбавляй.
Отже, де-то в ідеальному світі існує ідеальний метаморфный генератор, породжує абсолютно недетектируемый код. І там же, на противагу йому, існує ідеальний емулятор, на якому можна виконати цей метаморфный код і продетектировать його. Є ще розвиток теми самомодифицирующегося коду?

Філософські питанняСпробуємо розширити тему модифікації виконуваного коду в кожному новому поколінні. Адже саме мінливість кожного нового покоління лежить в основі еволюції, тому, тема самомодифицирующегося коду в аспекті інформаційних технологій пахне вже креационизмом. Хто знає, можливо ми поступово створюємо нову всесвіт і нове життя?

Розглядаючи шифрування буфера з основним кодом вірусу ми говорили про зміну вірусу з точки зору байт — тобто змінювався набір байт, складових вірус. Це самий примітивний рівень, окремий байт несе трохи інформації про властивості вірусу і змінами на цьому рівні не досягти варіативності поколінь. Якщо проводити аналогію з розвитком життя, це нагадує різноманіття простих хімічних сполук. Безліч простих сполук, в різних комбінаціях, вода, аміак, вуглекислий газ, кислотні залишки і гідроксильні групи — протягом мільйонів років цей коктейль не міг породити нічого складного. Але врешті-решт вдалі комбінації призвели до появи складних органічних молекул — основи життя.

Розглядаючи мутацію декриптора з допомогою метаморфного генератора ми в загальному розглядали вірус вже як набір інструкцій, а не байт. Це важливий факт, який означає, що тепер ми працюємо з інформаційними елементами такого порядку. Тепер ми працюємо з «обнуляем eax» замість того, щоб вдаватися в подробиці, як ми це робимо (xor eax,eax або sub eax, eax) і всі наші дізассемблер-детектори — це заміна детектування по байтам детектуванням по ланцюжку інструкцій. Замінюючи одну інструкцію іншій, ми змінюємо в тому числі і набір байт, тобто цей рівень включає в себе попередній, і, з точки зору еволюції видів, він куди більш просунутий. Зміною набору інструкцій можна домогтися більш цілеспрямованої варіативності, ніж тупим псевдо-випадкових «перемішуванням» байт. Біологічним аналогом, напевно, могли б виступити амінокислоти, кожна з яких вже сама по собі здатна породжувати деякий біологічну дію, при цьому вони цілісні, вміють комбінуватися в більш складні структури і несуть в собі більший квант інформації, ніж найпростіші з'єднання.

Якщо продовжувати аналогію, наступним рівнем, на якому вірус може змінюватися від покоління до покоління, будуть функції, тобто сутності, состоящиие з набору інструкцій. Це щось типу величезної колекції функцій у декількох примірниках на кожний окремий шматок функціоналу, наприклад, безліч функцій, які шукають файл для зараження, зашифровують і розшифровують тіло вірусу, інфікують файл-жертву і т.п. В кожному новому поколінні вірусу набір використовуваних функцій рекомбинируется, змінюючи весь внутрішній функціонал. Кожна функція написана окремим способом, містить в собі інший набір інструкцій і байт, тобто цей рівень також включає в себе всі попередні. При можливості реалізувати таку схему без архітектурних вразливостей, такий вірус можна буде теоретично детектувати тільки детектором, який вміє працювати з кодом також на рівні функцій, тобто, фактично, оцінює поведінковий сценарій вірусу, а не окремі інструкції. Не впевнений, що зараз існує щось схоже на движок, вміє мутувати на рівні функцій, хоча в декількох статтях читав про ідеї, наприклад, скачування вірусом власних частин з хоста вирмейкера і заміщення власного функціоналу, або генетичних алгоритмах (коли два вірусу змінюються функціональними блоками між собою). Тим не менш, величезна кількість різних високорівневих мов і фреймворків по ідеї повинно сприяти появі такого роду програм, але вже, як мені здається, не в області вірусів (нижче розповім чому). Напевно, біологічної аналогією функцій такого вірусу можуть бути білки. Внутрішньоклітинні функції можуть забезпечуватися різними типами білків, білки можуть заміщатися іншими, при цьому клітина залишиться цілісною, і буде продовжувати існування, незважаючи на змінені внутрішні функції.

Ну а вище тільки мутація на рівні загального алгоритму програми. Наприклад вірус, який зараз детектується антивірусом в наступному поколінні стає взагалі іншою програмою, доброю і пухнастою, і антивірусу його просто не потрібно детектувати, хехе. Хотілося б розтектися думкою, як у попередньому абзаці, але… Це фантастика.

Бабло перемагає злоНу і де це все, запитаєте ви? Де тисячі страшних метаморфных генераторів, що заполонили компи користувачів, які відправили у психіатричні клініки сотні програмістів антивірусних компаній, де страшні вірусні епідемії кладуть мережу на тижні, де це все? На мою думку причин тут декілька.

Перша причина, технічна — це обмеження середовища. До NT-шного ядра, NTFS і Linux на домашніх PC кодом вірусів жилося дуже привільно — пиши куди хочеш, исполняйся де хочеш. Зараз використовувати заражену систему набагато складніше і не так вже цікаво. Ні, я не хочу сказати, що все втрачено, але про колишню могутність залишилося тільки мріяти і з кожним роком ситуація все гірше — права на файли і процеси, підпису файлів, online-валідація запускається софта — все це практично вбило «чистокровні» комп'ютерні віруси. Але хто знає, за звітами ринок мобільних зараз росте досить активно, чи не означає це, що мобільним розробникам доведеться пройти по тих самих граблях, що і розробникам великих ОС? Дуже сподіваюся, що це не так, і технології захисту в повній мірі перекочували на мобільні пристрої.

Ну а головне, як мені здається, причина — це кваліфікація програміста. Якщо ви в змозі написати хороший метаморфный генератор, з яким фахівці провозяться хоча б кілька днів, або емулятор детектирующий сигнатуру всередині якісного вірусного движка, або зробити якісний crackme, який публічно почнуть поважати, то… просто напишіть мені. Я не рекрутер, але якщо вас буде багато — зміню професію, просто представляючи вас компаніям, які займаються безпекою. Будьте впевнені, ваші доходи і стабільність життя у багато разів перевищують те, що можна отримати поширюючи віруси або зламуючи софт. В цьому і є головна причина — просунуті движки пишуть дуже поодинокі ентузіасти з дуже хорошою підготовкою, і, в більшості випадків, знаходять в житті куди більш привабливі застосування своєму таланту. Ну а ще, на мою думку, комерційна частина сучасної зарази відверто нудна, тому ті, хто піддався спокусі отримувати крадені гроші, надовго зависають у суто прикладних аспектах і кидають займатися удосконаленням внутрішніх алгоритмів.

ЕпілогШкода, що кодом, здатного породжувати власні змінені копії, поки не знайшлося іншого застосування, окрім як ховатися від антивірусів і протистояти злому. Я щиро бажаю цим методам подальшого розвитку, бажано без ухилу в зловредство. І нехай програми, породжені людським розумом, стануть трохи ближче до того, що ми називаємо життям.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.