1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
| code_seg segment
TSRprog proc far
assume cs:code_seg
org 100h
begin:
jmp start
;----------------------------------------
New_2F Proc ;обработка мультиплексного прерывания
cli ;на всякий случай запретим на время все прерывания в ОС
cmp ax,08000h ;проверим, а нашу ли программу вызывают?
jne exit ;если нет, то выйти из процедуры.
mov ax, 0ABCDh ;иначе – отправить пароль «я здесь» (см. выше п1).
sti ;разрешить опять все прерывания в ОС.
iret ;выйти из прерывания
exit:
sti ;разрешить опять все прерывания в ОС.
jmp cs:[Old_2F] ;и перейти по адресу старого обработчика прерывания 2F.
Old_2F DD 0 ;это место под адрес старого обработчика прерывания 2F.
New_2F endp
;----------------------------------------
New_1C Proc
cmp cs:Cnt,182 ;проверяем прошло ли 10 сек.?
jb M2 ;если нет, то переход на увеличение счетчика (jb -переход при анализе беззнаковых величин)
mov cs:flg_timer,1 ;сообщение можно выводить, поэтому установим в ячейке флага истечения контрольного времени 1
M2:
inc byte ptr cs:Cnt ;увеличить счетчик времени на 1
jmp cs:[Old_1C] ;и перейти по адресу старого обработчика прерывания 2F.
Old_1C DD 0 ;это место под адрес старого обработчика прерывания 2F.
Cnt DB 0 ;ячейка для счетчика тиков
flg_timer DB 0 ;ячейка для флага истечения заданного времени
New_1C endp
;----------------------------------------
New_28 proc
;В первую очередь сохраним регистр флагов процессора, потому что сейчас
;будем проводить проверку на истечение заданного времени, что изменит
;регистр флагов
pushf
;теперь проверим – в ячейке флага контроля истечения заданного времени
;установлена единица?
cmp cs:flg_timer,1
;если нет, то время до вывода сообщения на экран еще не истекло и мы перехо-
;дим на старый обработчик прерывания 28h
jne M1
;в противном случае активизируем модуль вывода сообщения на экран. Для чего
;сначала сохраним в стеке регистры, которые сейчас начнем использовать. Иначе
;мы испортим в них данные прерванной нами программы.
push ax
push bx
push bp
push es
push cx
;Запретим прерывания нашей программы другими программами
cli
;========================================
mov ah, 0Fh ;Читаем текущий видео режим
int 10h ;bh <- номер активной страницы дисплея
mov ax,cs ;
mov es,ax ;Выводимая строка
mov bp,offset curtime ;
mov cl,25 ;Количество строк
call random ;Вызов random
mov dh,al ;Строка начала вывода
mov cl,73 ;Количество колонок - количество символов (80-7)
call random ;Вызов random
mov dl,al ;Колонка начала вывода
mov bl,2Eh ;Атрибуты строки: желтые буквы на зеленом фоне
mov cx,7 ;Длина строки
mov ax,1300h ;Вывод
int 10h ;
jmp q1
random:
;Сохраняем в стек регистры
push es
push bx
push dx
push cx
mov ah,0 ;Читаем часы
int 1Ah ;cx - старшая часть значения
;dx - младшая часть значения
mov es,cx ;
mov bx,dx ;
mov al,byte ptr es:[bx] ;Возвращаемое значение
pop cx ;Восстанавливаем
l1:
cmp al,cl ;Сравнение
jb l2 ;Если ниже
sub al,cl ;Иначе CL - AL
jmp l1 ;Повторная проверка
l2:
;Восстанавливаем сохраненные регистры
pop dx
pop bx
pop es
ret
;========================================
;Внесем в регистр ES адрес начала видеопамяти ПЭВМ. Для тех, кто подзабыл
; – обратитесь к справочнику TECH HELP там указано, что видеопамять начинается
;с сегмента 0B800h. Поскольку сразу число в регистр ES занести нельзя, восполь-
;зуемся регистром BX как промежуточным буфером
; mov bx,0B800h
; mov es,bx
;Теперь загрузим смещение адреса выводимой текстовой строки в регистр BP
;(можно было – в любой другой, который может использоваться в качестве
;регистра смещения, например si или di, но нам понравился BP)
; lea bp,curtime
;Регистр BX выберем в качестве регистра смещения индекса буквы на экране
;и занесем в него начальное значение 0
; mov bx,0
;установим счетчик для цикла в 7 (по количеству букв в выводимой строке curtime)
; mov cx,7
;Пометим начало цикла вывода строки текста на экран меткой vivod_on
;vivod_on:
;Возьмем из строки текста, подготовленного нами для вывода, первую букву
; mov al,byte ptr cs:[bp]
;и поместим ее на экран в первую экранную текстовую ячейку
; mov byte ptr es:[bx],al
;увеличим смещение BX на единицу, чтобы перейти к байту атрибутов текста на
;экране. Известно, что каждый символ текста на экране занимает в видеопамяти
;ПЭВМ два байта (слово). Младший байт хранит код самого символа, а старший –
;его цвет и цвет его фона (см. TECH HELP).
; inc bx
;Занесем в байт атрибута символа его цвет: желтые буквы по зеленому фону
;(кодировка цвета символа и его фона тоже см. в TECH HELP).
; mov al,2Eh
; mov byte ptr es:[bx],al
;Увеличим счетчик букв на экране на 1
; inc bx
;Увеличим счетчик букв в выводимой текстовой строке тоже на 1
; inc bp
;Повторим цикл для вывода следующего символа
; loop vivod_on
;После вывода всей строки, сбросим флаг готовности таймера и его счетчик,
;чтобы начался новый отсчет времени
q1:
mov cs:flg_timer,0
mov cs:cnt,0
;разрешим внешние прерывания
sti
;и восстановим все сохраненные регистры ОБЯЗАТЕЛЬНО в обратной последова-
;тельности их запоминания
pop cx
pop es
pop bp
pop bx
pop ax
M1:
popf
;В заключение процедуры передадим управление старому обработчику
jmp cs:[old_28]
;А это область данных нашей процедуры New_28. Здесь хранится текстовая стро-
;ка выводимая на экран ПЭВМ
curtime db "Message"
old_28 dd 0
New_28 endp
;----------------------------------------
start:
;Во-первых, сразу запросим прерывание 2F – нет ли нашей программы в памяти?
;Для этого занесем в AX запланированный нами код функции (см. выше процедуру
;New_2F – мы там решили занять для своих нужд функцию 80 и подфункцию 00
mov ax,08000h
;Обратимся к функции 2F
int 2Fh
;Проверим поступивший из прерывания «отзыв». Наша программа должна ото-
;зваться кодом 0ABCD (см.выше процедуру New_2F)
cmp ax,0ABCDh
;Если это так, то мы пытаемся второй раз установить программу – это не нужно,
;а может быть мы хоти выгрузить ее из памяти? Надо проверить ключ в строке
;параметров запуска программы, поэтому перейдем к метке ANALIZ
je analiz
;В противном случае начинаем установку программы резидентом в памяти
;Для этого сначала сохраним адреса старых векторов прерываний, которые мы
;планируем заместить своими процедурами. Начнем с вектора 28h. Используя
;стандартную функцию 35h прерывания 21h получим этот вектор в ES:BX
mov ax,3528h
int 21h
;и сохраним его в специально подготовленной ячейке old_28 (см.выше)
mov word ptr old_28,bx
mov word ptr old_28+2,es
;С помощью стандартной функции 25h прерывания 21h на место старого векто-
;ра установим адрес нашей процедуры
mov ax,2528h
lea dx,new_28
int 21h
;Теперь то же самое проделаем с вектором 2Fh
mov ax,352Fh
int 21h
mov word ptr old_2F,bx
mov word ptr old_2F+2,es
mov ax,252Fh
lea dx,New_2F
int 21h
;и с вектором 1Сh
mov ax,351Ch
int 21h
mov word ptr old_1C,bx
mov word ptr old_1C+2,es
mov ax,251Ch
lea dx,New_1C
int 21h
;Разместим в регистре dx адрес начала НЕ РИЗЕДЕНТНОЙ части нашей программы
lea dx,start
;и, с помощью стандартного прерывания 27h завершим нашу программу, оставив
;ее в памяти резидентной
int 27h
;Сюда мы попадаем, если наша программа обнаружена уже установленной в памяти
analiz:
;Будем анализировать содержимое строки параметров нашей программы.
;Известно, что после запуска программы, содержимое командной строки операц-
;онная система размещает по смещению 80h в PSP программы (см. TECH HELP,
;PSP). Точнее по самому смещению 80h находится байт, в котором размещается
;число, равное количеству символов в строке параметров. Получим его из PSP,
;для чего смещение интересующего нас байта занесем в SI
mov si,80h
;потом загрузим этот байт в AL
lodsb
;и проверим, длина строки параметров не меньше трех символов? (Если Вы еще
;не забыли, мы решили выгружать программу, если подадим в командной строке
;параметр /u, а это, вместе с пробелом – как раз три символа)
cmp al,3
;если меньше, то выйти из программы, потому, что командная строка коротка
jl bue
;иначе перейдем на адрес следующего байта командной строки
inc si
;и возьмем из нее в AX два байта (слово)
lodsw
;проверим, это наш ключ /u ? (при этом помним, что в AX байты перевернуты –
;сначала заносится старший, потом младший
cmp ax,'u/'
;если не равно, то это не наш ключ и программу завершаем
jne bue
;В противном случае начинаем модуль выгрузки программы из памяти
;Для начала загрузим регистры ES:BX адресом нашей программы в памяти
;для чего возьмем вектор любого из контролируемых ею прерываний например 28h
mov ax,3528h
int 21h
;Теперь сохраним регистр ds, так как сейчас начнем его портить
push ds
; загрузим регистры DS:DX адресом старого вектора прерывания 1С взятого
;из памяти нашей программы, которая сейчас стоит резидентом.
lds dx,dword ptr es:old_1C
;и возвратим его на «родное место» в таблицу векторов прерываний
mov ax,251Ch
int 21h
;То же сделаем с прерыванием 28h
lds dx,dword ptr es:old_28
mov ax,2528h
int 21h
;И прерыванием 2F
lds dx,dword ptr es:old_2F
mov ax,252Fh
int 21h
;Восстановим регистр DS
pop ds
;Используя стандартную функцию 49h прерывания 21h, освободим память, зани-
;маемую нашей резидентной программой. Функция освобождает память, сегмент
;которой стоит в ES. А там как раз адрес начала нашей резидентной программы.
mov ah,49h
int 21h
;Теперь надо освободить память, занятую блоком окружения программы. Надеюсь
;Вы помните, что при запуске программы операционная система создает два бло-
;ка управления памятью (MCB) – один для самой программы, а второй для тексто-
;вых строк, взятых их файла autoexec.bat с ключем set. Этот блок называется
;«блоком окружения» и его сегментный адрес операционная система заносит в
;PSP программы в слово по смещению 02Ch от начала PSP. Возьмем оттуда этот
;адрес и поместим его в ES
mov es,word ptr es:[002ch]
;После чего дадим команду освободить этот блок
mov ah,49h
int 21h
;Теперь можно завершить программу и возвратиться в операционную систему
bue:
mov ax,4c00h
int 21h
TSRprog endp
code_seg ends
end begin |