Опыт дизассемблирования большой .com программы
Содержание:
- Как мыслит процессор
- Нахождение всех текстовых строк внутри EXE файла
- Архитектуры RISC
- Внешний осмотр
- Методы защиты от дизассемблеров
- Компиляция по примеру условного выражения
- Parameters
- Инcтрукция if
- Архитектура виртуальной машины ассемблера
- Зависимость от транслятора
- Язык. Преимущества и отличия от ЯВУ
- Заключение
- Заключение
Как мыслит процессор
Чтобы понять, как работает Ассемблер и почему он работает именно так, нам нужно немного разобраться с внутренним устройством процессора.
Кроме того, что процессор умеет выполнять математические операции, ему нужно где-то хранить промежуточные данные и служебную информацию. Для этого в самом процессоре есть специальные ячейки памяти — их называют регистрами.
Регистры бывают разного вида и назначения: одни служат, чтобы хранить информацию; другие сообщают о состоянии процессора; третьи используются как навигаторы, чтобы процессор знал, куда идти дальше, и так далее. Подробнее — в расхлопе ↓
Какими бывают регистры
Общего назначения. Это 8 регистров, каждый из которых может хранить всего 4 байта информации. Такой регистр можно разделить на 2 или 4 части и работать с ними как с отдельными ячейками.
Указатель команд. В этом регистре хранится только адрес следующей команды, которую должен выполнить процессор. Вручную его изменить нельзя, но можно на него повлиять различными командами переходов и процедур.
Регистр флагов. Флаг — какое-то свойство процессора. Например, если установлен флаг переполнения, значит процессор получил в итоге такое число, которое не помещается в нужную ячейку памяти. Он туда кладёт то, что помещается, и ставит в этот флаг цифру 1. Она — сигнал программисту, что что-то пошло не так.
Флагов в процессоре много, какие-то можно менять вручную, и они будут влиять на вычисления, а какие-то можно просто смотреть и делать выводы. Флаги — как сигнальные лампы на панели приборов в самолёте. Они что-то означают, но только самолёт и пилот знают, что именно.
Сегментные регистры. Нужны были для того, чтобы работать с оперативной памятью и получать доступ к любой ячейке. Сейчас такие регистры имеют по 32 бита, и этого достаточно, чтобы получить 4 гигабайта оперативки. Для программы на Ассемблере этого обычно хватает.
Так вот: всё, с чем работает Ассемблер, — это команды процессора, переменные и регистры.
Здесь нет привычных типов данных — у нас есть только байты памяти, в которых можно хранить что угодно. Даже если вы поместите в ячейку какой-то символ, а потом захотите работать с ним как с числом — у вас получится. А вместо привычных циклов можно просто прыгнуть в нужное место кода.
Нахождение всех текстовых строк внутри EXE файла
Дизассемблер отображает все найденные в сегменте данных текстовые строки в отдельной закладке Strings. Если вы пытаетесь найти какие-то зацепки в изучаемом файле в виде осмысленного текста, подобный список строк может дать вам подсказки о назначении тех или иных функций и процедур, вызываемых файлом, или даже какую-нибудь информацию о происхождении файла.
В отличие от существующих разнообразных утилит, занимающихся поиском и нахождением текстовых строк в исполняемых файлах, PE Explorer делает это аккуратнее и качественнее, поскольку опирается в своем поиске на результаты анализа кода кода приложения.
Архитектуры RISC
MCS-51
MCS-51 (Intel 8051) — классическая архитектура микроконтроллера. Для неё существует кросс-ассемблер ASM51, выпущенный корпорацией MetaLink.
Кроме того, многие фирмы-разработчики программного обеспечения, такие, как IAR или Keil, представили свои варианты ассемблеров. В ряде случаев применение этих ассемблеров оказывается более эффективным благодаря удобному набору директив и наличию среды программирования, объединяющей в себе профессиональный ассемблер и язык программирования Си, отладчик и менеджер программных проектов.
AVR
На данный момент для AVR существуют 4 компилятора производства Atmel (AVRStudio 3, AVRStudio 4, AVRStudio 5 и AVRStudio 6, AVRStudio 7).
В рамках проекта AVR-GCC (он же WinAVR) существует компилятор avr-as (это портированный под AVR ассемблер GNU as из GCC).
Также существует свободный минималистический компилятор avra.
Платные компиляторы: IAR (EWAVR), CodeVisionAVR, Imagecraft. Данные компиляторы поддерживают языки Assembler и C, а IAR ещё и C++.
Существует компилятор с языка BASIC — BASCOM, также платный.
ARM
Поставщик IDE | Компилятор | Поддерживаемые языки | Условия использования |
---|---|---|---|
Си/C++/Assembler | Shareware (не более 32kb) | ||
Си/C++/Assembler | Commercial | ||
Си/C++/Assembler. | |||
Си/C++/Assembler |
PIC
Средой разработки, выпускаемой компанией Microchip Technology для создания, редактирования и отладки программ для микроконтроллеров семейства PIC, является MPLAB. Среда включает в себя трансляторы с языка ассемблер MPASM и ASM30 для различных семейств микроконтроллеров PIC. Современные версии среды «MPLAB X IDE» являются мультиплатформенными и работают под различными операционными системами для ЭВМ. Среда распространяется бесплатно.
Внешний осмотр
Кратко: на этом этапе производится внешний осмотр устройства с целью поиска маркировок, доступных разъемов.
- Микроконтроллер STM32F042 – тут сразу стоит обратиться к документации на микроконтроллер (если такая есть), откуда можно узнать архитектуру, разрядность микроконтроллера и много чего полезного (для нашего случая – 32 разрядный микроконтроллер на архитектуре ARM);
- На тыльной стороне имеется разъем без обозначений – те, кто работал с микроконтроллерами, могут сделать верное предположение, что это разъем для прошивки устройства (во-первых, он не промаркирован; во-вторых, он имеет 5 контактов, что соответствует необходимому количеству контактов для перешивки микроконтроллера);
- Контакты GND, TX;
- USB-разъем для питания устройства (об этом говорится и в «Инструкции»);
- Неизвестный разъем XP2 на лицевой стороне устройства;
- Непонятная желтая блямба на ноге носорога – вероятно, сенсорная кнопка.
RHINOCEROS-220x
Методы защиты от дизассемблеров
Для защиты от дизассемблеров
используются различные методы, большинство из
которых базируется на использовании «принципа
фон Неймана», который заключается в том, что
программы и данные выглядят и хранятся
одинаково, в результате чего программа может
модифицировать саму себя. Использования таких
методов чаще всего достаточно для защиты от
дизассемблеров.
Ниже приведены некоторые приемы,
которые следует использовать для
противодействия дизассемблерам.
1. Шифрование критичного кода программы
и дешифрация его самой системой защиты перед
передачей управления на него. Таким образом,
дешифрация программы происходит не сразу, а
частями и защита от дизассемблера оказывается
распределенной во времени. При этом никогда не
осуществляийте дешифрацию одной подпрограммой,
т.к. ее будет легко вычислить и отключить. Также
следует затирать те участки программы, которые
уже не понадобятся. Шифрование исполняемого кода
программы с целью защиты от дизассемблера
является наиболее простым средством как в смысле
его реализации, так и в смысле снятия. Шифрование
может быть использовано лишь как часть защиты от
дизассемблера и поэтому необязательно должно
быть сложным.
2. Скрытие команд передачи управления
приводит к тому, что дизассемблер не может
построить граф передачи управления.
2.1. Косвенная передача управления.
2.2. Модификация адреса перехода в коде
программы (таблица 1).
Таблица 1
Действие | Код |
Переходы и вызовы подпрогамм по динамически изменяемым адресам подразумевают модификацию байтов адреса перехода или вызова подпрограммы, находящихся за первым байтом команды ( байтом кода операции ). |
|
. . .
mov . . . m: jmp place . . . |
модификация адреса перехода - подстановка нового адреса |
. . .
mov mov word ptr cs:m,5678h . . . m: call far 0000h . . . |
модификация адреса вызываемой подпрограммы |
mov bx,1234h
jmp dword ptr cs: . . . ;или call word ptr es: |
модифицировать адрес гораздо проще, если использовать косвенные переходы и вызовы |
and byte ptr cs:m, 0EFh ;обнулить
;4ый байт ;по адресу m m: push ax ;команда преобразуется ;в inc ax |
2.3. Использование нестандартных
способов передачи управления (jmp через ret, ret и call
через jmp) (таблицу 2).
Таблица 2
Первичный код | Альтернативный код |
. . .
jmp m . . . m: |
. . .
mov ax,offset m ret ; перейти на метку . . . m: |
. . .
call subr m: . . . subr: . . . ret |
mov ax,offset m ; занести в стэк
push ax ; адрес возврата. jmp subr ; перейти на под- m: ; программу . . . subr: . . . ret |
. . .
int |
. . .
pushf push cs ; занести в стэк CS. mov ax,offset m ; занести в стэк push ax ; адрес возврата. xor ax,ax mov es,ax jmp dword ptr es: m: . . . |
. . .
ret |
. . .
; pop bx ; адрес возврата и jmp bx ; перейти на него |
iret
. |
mov bp,sp ; переход на точку
jmp dword ptr ; возврата из пре- ; рывания. . . . add sp,4 ; точка возврата. popf |
-
Перекрывающийся код. Расмотрим
следующий пример:
eat_sr:
mov ax,02EBh
jmp $-2 ; huh?
... ; rest of code остальной код
Первая инструкция заносит
«незначимое» значение в AX. Вторая делает
переход на значение операнда команды MOV AX. ’02EB’
переводится как ‘jmp$+2’. Этот переход перепрыгивает
первый JMP и продолжает дальше по коду. Вот еще
один пример:
erp: mov ax,0FE05h
jmp $-2h
add ah,03Bh
... ; rest of code
Этот код более полезен. Смоделируем
трассировку, показвыая HEX-дамп каждый шаг, чтобы
прояснить ситуацию.
B8 05 FE EB FC 80 C4 3B mov ax,0FE05h ; ax=FE05h
^^ ^^ ^^
B8 05 FE EB FC 80 C4 3B jmp $-2 ; jmp into '05 FE'
^^ ^^
B8 05 FE EB FC 80 C4 3B add ax,0EBFEh ; 05 is 'add ax'
^^ ^^ ^^
B8 05 FE EB FC 80 C4 3B cld ; a dummy instruction
^^
B8 05 FE EB FC 80 C4 3B add ah,3Bh ; ax=2503h
^^ ^^ ^^
Инструкция ADD AH,03Bh здесь означает
просто занесение 2503h в AX. Добавив 5 байт (вместо
простого использования ‘mov ax,2503h’), этот код очень
хорошо затруднит работу дизассемблеру. Даже если
инструкции дизассемблированы верно, значение AX
будет неизвестно до тех пор, пока не будет
помещено в AX. Вы можете скрывать значение от
дизассемблера, используя ‘ADD AX’ или ‘SUB AX’ везде, где
это только возможно. Если Вы хорошо проверите
это, Вы сможете увидеть, что любое значение может
быть занесено в AX. Два этих значения могут быть
изменены на 0FEh в первой строке и 03Bh — в последней.
4. Использование возможностей
установки префикса сегментного регистра перед
некоторыми командами (pushf, pushfd, cld и др.).
Дизассемблер не в состоянии правильно
распознать программу (db 3Eh, 2Eh, 90h = ds: cs: nop).
5. Дизассемблер сбивается на
нестандартном формате загружаемого модуля
(например, перекрыть весь сегмент кода exe-файла DOS
стеком).
Компиляция по примеру условного выражения
Дизассемблированный вариант в radare2
1605793045836.png
1 — инициализации переменной var_4h (i = 3)2 — выполнения инструкций (add, printf)
Чтобы понять какой «case» выбран, происходит сравнение (cmp, а затем je, jne) переменной i с значением case.
Режим графов
1605793347813.pngscreen13.pngscreen_13_2.png
Глядя на этот код, сложно (если вообще возможно) сказать, что представлял собой оригинальный исходный текст — конструкцию switch или последовательность выражений if . В обоих случаях код выглядит одинаково, поскольку оба выражения используют множество инструкций cmp и je или jne.
Parameters
The following options are available for .exe, .dll, .obj, .lib, and .winmd files.
Option | Description |
---|---|
/out= | Creates an output file with the specified , rather than displaying the results in a graphical user interface. |
/rtf | Produces output in rich text format. Invalid with the /text option. |
/text | Displays the results to the console window, rather than in a graphical user interface or as an output file. |
/html | Produces output in HTML format. Valid with the /output option only. |
/? | Displays the command syntax and options for the tool. |
The following additional options are available for .exe, .dll, and .winmd files.
Option | Description |
---|---|
/bytes | Shows actual bytes, in hexadecimal format, as instruction comments. |
/caverbal | Produces custom attribute blobs in verbal form. The default is binary form. |
/linenum | Includes references to original source lines. |
/nobar | Suppresses the disassembly progress indicator pop-up window. |
/noca | Suppresses the output of custom attributes. |
/project | Displays metadata the way it appears to managed code, instead of the way it appears in the native Windows Runtime. If is not a Windows metadata (.winmd) file, this option has no effect. See .NET Framework Support for Windows Store Apps and Windows Runtime. |
/pubonly | Disassembles only public types and members. Equivalent to /visibility:PUB. |
/quoteallnames | Includes all names in single quotes. |
/raweh | Shows exception handling clauses in raw form. |
/source | Shows original source lines as comments. |
/tokens | Shows metadata tokens of classes and members. |
/visibility: | Disassembles only types or members with the specified visibility. The following are valid values for :PUB — PublicPRI — PrivateFAM — FamilyASM — AssemblyFAA — Family and AssemblyFOA — Family or AssemblyPSC — Private Scope For definitions of these visibility modifiers, see MethodAttributes and TypeAttributes. |
The following options are valid for .exe, .dll, and .winmd files for file or console output only.
Option | Description |
---|---|
/all | Specifies a combination of the /header, /bytes, /stats, /classlist, and /tokens options. |
/classlist | Includes a list of classes defined in the module. |
/forward | Uses forward class declaration. |
/headers | Includes file header information in the output. |
/item: ] | Disassembles the following depending upon the argument supplied: — Disassembles the specified .- Disassembles the specified of the .- Disassembles the of the with the specified signature . The format of is: [] (, , …, )Note In the .NET Framework versions 1.0 and 1.1, must be followed by a closing parenthesis: . Starting with the Net Framework 2.0 the closing parenthesis must be omitted: . |
/noil | Suppresses IL assembly code output. |
/stats | Includes statistics on the image. |
/typelist | Produces the full list of types, to preserve type ordering in a round trip. |
/unicode | Uses Unicode encoding for the output. |
/utf8 | Uses UTF-8 encoding for the output. ANSI is the default. |
The following options are valid for .exe, .dll, .obj, .lib, and .winmd files for file or console output only.
Option | Description |
---|---|
/metadata | Shows metadata, where is:MDHEADER — Show the metadata header information and sizes.HEX — Show information in hex as well as in words.CSV — Show the record counts and heap sizes.UNREX — Show unresolved externals.SCHEMA — Show the metadata header and schema information.RAW — Show the raw metadata tables.HEAPS — Show the raw heaps.VALIDATE — Validate the consistency of the metadata. You can specify /metadata multiple times, with different values for . |
The following options are valid for .lib files for file or console output only.
Option | Description |
---|---|
/objectfile= | Shows the metadata of a single object file in the specified library. |
Note
All options for Ildasm.exe are case-insensitive and recognized by the first three letters. For example, /quo is equivalent to /quoteallnames. Options that specify arguments accept either a colon (:) or an equal sign (=) as the separator between the option and the argument. For example, /output: filename is equivalent to /output= filename.
Инcтрукция if
Данную инструкцию довольно просто отличить в дизассемблированном виде от других инструкций. Её отличительное свойство — одиночные инструкции условного перехода je, jne и другие команды jump.
1605790962062.png1605790989407.png
Напишем небольшую программу на языке Си и дизассемблируем её с помощью radare2. Разницы между IDA PRO и radare2 при дизассемблировании этих программ не было обнаружено, поэтому я воспользуюсь radare2. Вы можете использовать IDA PRO.
Компилируем при помощи gcc. Команда . -m32 означает, что компилироваться код будет под архитектуру x86.
Чтобы посмотреть на код в radare2, напишем команду . Далее прописываем для анализа кода и переходим к функции main . Посмотрим на код с помощью команды .
1605792900362.png
Первым делом в программе происходит объявление переменных ( int x; int y ), а затем значение 1 перемещается в var_ch (это переменная x) и значение 2 в var10h (это переменная y). Далее идёт сравнение (cmp) 1 и 2 (). Эти значения не равны. Значит jne ( jump if not equal) перейдёт по адресу 0x000011e1. Проще всего инструкцию if запомнить и определить в режиме графов (команда для для radare2 или клавиша пробел для IDA).
1605792914861.png
Немного усложним задачу. Добавим вложенные инструкции. Попробуйте проанализировать этот код.
Архитектура виртуальной машины ассемблера
Ассемблер fasmg (архитектура CALM-инструкций)
Является наследником ассемблера flat assembler (fasm) со схожим синтаксисом, но в отличие от fasm не привязан ни к какой архитектуре процессора. Его парадигмой является формирование посредством макросов выходных файлов любых форматов и с машинным кодом под любые архитектуры процессоров. Помимо макросов в fasmg присутствуют т. наз. CALM-инструкции (букв. «инструкции компилируемой сборки, подобные макросам») — собственные инструкции виртуальной машины ассемблера, эквивалентные макросам, которые преобразуются транслятором в байт-код. Архитектуру этих CALM-инструкций можно считать «родной» архитектурой ассемблера fasmg. В комплекте имеются наборы CALM-инструкций для эмуляции поддержки инструкций архитектур x86, x64, 8052, AVR; разработчиком могут быть описаны наборы CALM-инструкций для поддержки любой другой архитектуры, поддержки любых выходных форматов файлов. Имеются вариации транслятора для Mac OS, Linux и Windows.
Зависимость от транслятора
Программисты на ассемблере склонны пренебрегать правилами хорошего тона, нарушать все мыслимые табу, и это создает дополнительные трудности при дизассемблировании. В качестве примера приведем фрагмент кода, выданного дизассемблером
s25 proc near inc cx ;0086b add di,bp ;0086c adc si,00 ;0086e add dx,si ;00871 push di ;00873 shl di,1 ;00874 ;Multiply by 2's adc dx,00 ;00876 pop di ;00879 ret ;0087a
Этот фрагмент представляется совершенно невинным, и действительно, он дизассемблирован правильно. Вся беда в том, что программист задумал изменять этот фрагмент, то есть резать по живому. Оказывается, в программе есть еще такой кусок
mov di,086bh ;007f8 ...................................... mov BYTE PTR ,4ah ;00800 mov BYTE PTR ,0f1h ;00803 mov BYTE PTR ,0d1h ;00807 ...................................... ret ;00815
Так как di используется для косвенной адресации, нам прежде всего необходимо заменить 086bh на соответствующий OFFSET d0086b и пометить этой меткой начало подпрограммы s25:
s25 proc near d0086b: inc cx ;0086b ..............................................
Далее следует понять, что делают инструкции, приведенные на рис.1 с подпрограммой s25. Пусть эта подпрограмма асслемблирована с помощью TASM 1.01. Выданный ассемблером код будет таким, как показано на рисунке 2.
41 INC CX 41 INC CX 03FD ADD DI,BP 01EF ADD DI,BP 83D600 ADC SI,0000 83D600 ADC SI,0000 03D6 ADD DX,SI 01F2 ADD DX,SI 57 PUSH DI 57 PUSH DI D1E7 SHL DI,1 D1E7 SHL DI,1 83D200 ADC DX,0000 83D2000 ADC DX,0000 5F POP DI 5F POP DI C3 RET C3 RET
Но вся беда в том, что исходная программа была ассемблирована другим ассемблером и имеет вид, показанный на рисунке 3. Как видно из сравнения рисунков 2 и 3, TASM 1.01 и неизвестный ассемблер транслируют инструкции ADD по-разному, и это приводит к катастрофическим последствиям. Действительно, посмотрим, как воздействует участок кода, показанный на Рис.1 (перед этим заменим 086bh на OFFSET d0086b) на подпрограмму s25, транслируемую TASMом (рис.4) и неизвестным ассемблером (рис.5).
4A DEC DX 4A DEC DX 03FD ADD DI,BP 01EF ADD DI,BP 83D600 ADC SI,0000 83D600 ADC SI,0000 03F1 ADD SI,CX ;!!!! 01F1 ADD CX,SI ;!!!! 57 PUSH DI 57 PUSH DI D1E7 SHL DI,1 D1E7 SHL DI,1 83D100 ADC CX,0000 83D100 ADC CX,0000 5F POP DI 5F POP DI C3 RET C3 RET
Сравнение рисунков 4 и 5 показывает, что логика работы программы меняется в зависимости от того, какой ассемблер применялся. Как выкрутиться из этой ситуации, если нужного ассемблера нет под рукой? Самый простой, но не очень красивый путь — поставить «заплатку». Чтобы можно было использовать TASM, подпрогроамма s25 должна выглядеть так:
s25 proc near d0086b: inc cx ;0086b add di,bp ;0086c adc si,00 ;0086e db 01,0f2 ;add dx,si !!!!!! ;00871 push di ;00873 shl di,1 ;00874 ;Multiply by 2's adc dx,00 ;00876 pop di ;00879 ret ;0087a
Язык. Преимущества и отличия от ЯВУ
- знание синтаксиса транслятора ассемблера, который используется (например, синтаксис MASM, FASM и GAS отличается), назначение директив языка ассемблер (операторов, обрабатываемых транслятором во время трансляции исходного текста программы);
- понимание машинных инструкций, выполняемых процессором во время работы программы;
- умение работать с сервисами, предоставляемыми операционной системой — в данном случае это означает знание функций Win32 API. При работе с языками высокого уровня очень часто к API системы программист прямо не обращается; он может даже не подозревать о его существовании, поскольку библиотека языка скрывает от программиста детали, зависящие от конкретной системы. Например, и в Linux, и в Windows, и в любой другой системе в программе на Си/Си++ можно вывести строку на консоль, используя функцию printf() или поток cout, то есть для программиста, использующего эти средства, нет разницы, под какую систему делается программа, хотя реализация этих функций будет разной в разных системах, потому что API систем очень сильно различается. Но если человек пишет на ассемблере, он уже не имеет готовых функций типа printf(), в которых за него продумано, как «общаться» с системой, и должен делать это сам.
Оптимальной
- объем используемой памяти (программы-загрузчики, встраиваемое программное обеспечение, программы для микроконтроллеров и процессоров с ограниченными ресурсами, вирусы, программные защиты и т.п.);
- быстродействие (программы, написанные на языке ассемблера выполняются гораздо быстрее, чем программы-аналоги, написанные на языках программирования высокого уровня абстракции. В данном случае быстродействие зависит от понимания того, как работает конкретная модель процессора, реальный конвейер на процессоре, размер кэша, тонкостей работы операционной системы. В результате, программа начинает работать быстрее, но теряет переносимость и универсальность).
языков высокого уровня абстракциисредах быстрого проектирования
Заключение
В целом, бесплатный аналог IDA Pro в лице Radare2 является довольно неплохим решением. Однако, официальный GUI Radare2 хоть и позволяет удобно перемещаться между инструментами Radare2 и в части отображения информации удобнее консольной версии, но в то же время он ещё недостаточно доработан и не предоставляет всех возможностей, которые можно реализовать через консоль. Со всеми возможностями консольной версии можно ознакомиться в официальной книге по Radare2.
Что касается обратной разработки, то он оказался совсем не страшным и даже при начальном уровне знания языка Ассемблер можно разбираться в устройстве какого-нибудь простенького приложения. А в Корпоративных лабораторияx Pentestit можно попробовать свои силы не только в реверс-инжинеринге бинарных файлов, но и в деассемблировании Android/IOS приложений.
Заключение
Приведенный список методов
противодействия дизассемблерам является
неполным, но вполне достаточным для
противодействия попыткам получить
дизассемблированный листинг программы.
Дополнительные методы защиты от дизассемблеров
можно найти в .
ЛИТЕРАТУРА
-
Расторгуев С. П., Дмитриевский Н. Н.
Искусство защиты и «раздевания программ». —
М., 1991. -
Шнайдер А. Язык ассемблера для
персонального компьютера фирмы IBM. Пер. с англ. —
М.: Мир, 1998. — 406 с. -
Правиков Д. И. Ключевые дискеты.
Разработка элементов систем защиты от
несанкционированного копирования. — М.: Радио и
связь, 1995. — 128 с. -
Спесивцев А. В., Вегнер В. А., Крутяков А.
Ю. и др. Защита информации в персональных ЭВМ. — М.:
Радио и связь, 1992. — 190 с. -
Щербаков А. Защита от копирования. — М.:
Эдель, 1992. — 80 с.
Кафедра вычислительной техники
Смоленский филиал Московского
энергетического института
(Технический университет)
Поступила в редакцию 28.11.99.