VR-Online (July 2007)

Page 1


Intro

За ОС Linux будущее. Переходите на эту ось. Многие крупные, а также муниципальные предприятия уже перешли или собираются переходить на Linux. Время идет и проблем в Linux становится все меньше, например, уже почти не ощущается дефицит ПО в этой ОС, как это было раньше. Так что собирайтесь с мыслями и переходите на эту замечательную бесплатную OC. Я в свою очередь, в своих статьях буду всячески вам пытаться помочь. Удачи в не легком переходе! P.S. Приятного чтения! KeyWarez Конец июля выдался жарким для всей VR-Group. Ни с того ни сего все начали активно чем-то заниматься. Romul, перевел сайт на представленный ранее дизинг, Horrific, стал вносить изменения в движок, KeyWarez вплотную подсел на Linux и теперь радует нас своими статьями, Lord_of_fear постоянно серфит инет в поисках очередных вкусных новостей, в LittleBudd’e проснулась страшная тяга к нашему форуму и теперь он активно там тусит. В общем, в июле активизировались все участники проекта. Тимовцы, которые не попали в перечисленный мной список, занимались написанием статей для этого номера, который, на мой взгляд, получился довольно неплохим. Мы выдержали объем последних номеров (100 страниц) и более качественно отнеслись к присланным статьям. Статьи, которые состояли из пары абзацев, и 15 килобайт кода я сразу отправил в треш и сообщил об этом их авторам. Ребята, поймите, не нужно из исходника делать статью. Читать 10-12 страниц кода никто не будет. Уж если вы хотите чтобы вашу работу увидели, то просто пришлите исходник с описанием, и мы с удовольствием выложим его в разделе «Скачать». Так будет удобнее для всех. На этом я хочу завершить эту интру и пожелать тебе приятного чтения. Все свои вопросы/комментарии по поводу журнала ты можешь оставлять на нашем форуме либо мне на мыло: antonov.igor.khv@gmail.com. Удачи! Spider_NET Идея: Фленов Михаил aka Horrific (horrific@vr-online.ru) Редактор номера: Spider_NET (antonov.igor.khv@gmail.com) Графика в журнале: KeyWarez (keywarez@yandex.ru), Spider_NET (antonov.igor.khv@gmail.com) Текущий состав VR-Group:

Horrific, LittleBudda, KeyWarez, Romul, tripsin, Lord_Of_Fear, Spider_NET. Контакты: mail@vr-online.ru, www.vr-online.ru Информация к размышлению: Данный журнал распространяется в виде PDF файлов. Вы можете выкладывать номера на любые носители без изменения внешнего вида журнала, без перевода в другие форматы, без изменения самого файла. В журнал запрещается вносить изменения. Перепечатка материалов запрещена. Журнал распространяется бесплатно, и ты можешь скачать его с нашего сайта, поэтому мы не видим смысла в перепечатывании материалов. Если ты хочешь стать автором журнала, то присылай свою статью на наш e-mail, и мы обязательно включим её в очередной номер.


Содержание номера: INTRO ............................................................................................................................................................................ 2 КОДИНГ ........................................................................................................................................................................ 4 УЧИМСЯ ДЕЛАТЬ AUTORUN НА DELPHI ......................................................................................................................... 6 DЕЛЬФИНИЙ ДZЕН №1 : САМОМОДИФИЦИРУЮЩИЙСЯ КОД .......................................................................................... 9 MIDLET PASCAL: РАБОТА С РЕСУРСАМИ: ПОДКЛЮЧЕНИЕ И ЧТЕНИЕ ............................................................................ 13 MIDLET PASCAL: ФОНОВАЯ МУЗЫКА ТВОЕГО ПРИЛОЖЕНИЯ. ...................................................................................... 15 ФОНОВАЯ МУЗЫКА В DELPHI ПРИЛОЖЕНИЯХ.............................................................................................................. 17 ВНЕДРЕНИЕ DLL В ЧУЖОЕ АДРЕСНОЕ ПРОСТРАНСТВО. ............................................................................................... 19 ВВЕДЕНИЕ В ЗАЩИТУ SHAREWARE-ПРОГРАММ. .......................................................................................................... 24 АТАКА НА DELPHI-ПРИЛОЖЕНИЯ ................................................................................................................................ 28 RIBBON В DELPHI........................................................................................................................................................ 36 ВВЕДЕНИЕ В ТЕОРИЮ КОНЕЧНЫХ АВТОМАТОВ ........................................................................................................... 42 СОЗДАНИЕ ИЗОБРАЖЕНИЙ С ИСПОЛЬЗОВАНИЕМ OPENGL. .......................................................................................... 46 СЧЕТЧИК ТРАФИКА НА DELPHI.................................................................................................................................... 56 ЯДРА – ЧИСТЫЙ ИЗУМРУД .......................................................................................................................................... 62 БОЕВЫЕ ДЕЙСТВИЯ В УСЛОВИЯХ ОГРАНИЧЕННОЙ ВИДИМОСТИ................................................................................... 70 БАЗЫ ДАННЫХ........................................................................................................................................................ 79 POSTGRE-SQL В LINUX............................................................................................................................................. 80 СОВМЕСТНЫЙ ДОСТУП К ДАННЫМ ............................................................................................................................. 86 БЕЗОПАСНОСТЬ БАЗ ДАННЫХ ПРЕДПРИЯТИЯ ............................................................................................................... 94 ГРАФИКА.................................................................................................................................................................. 101 БАННЕР СВОИМИ РУКАМИ ........................................................................................................................................ 102 БЕЗ РАМКИ............................................................................................................................................................... 105 ВИРТ ........................................................................................................................................................................ 106 СОРМ НА СЛУЖБЕ ГОСУДАРСТВА ............................................................................................................................ 109


Кодинг

От редактора: В этом номере рубрика «Кодинг» раздулась просто до неузнаваемости. В былые времена этот выпуск журнала можно было смело отнести к группе «Для программистов». Все авторы очень постарались, так что скучным материал не покажется. Сегодня в этой рубрике твоему вниманию представлены следующие работы (полный список доступен в оглавлении): • • • • • •

Учимся делать Autorun на Delphi Самомодифицирующийся код в Delphi Midlet Pascal: Работа с ресурсами Midlet Pascal: Фоновая музыка в твоем приложении Фоновая музыка в Delphi приложениях Внедрение DLL в чужое адресное пространств

Удачной компиляции!!!! J


Ты интересуешься управлением проектов и хочешь делать это грамотно? Тогда следующая книга будет очень полезной для тебя:

Аннотация:

Эта книга поможет вам освоить популярнейшее средство по управлению проектами MS Project, используемое миллионами людьми во всем мире. Она построена по принципу «просто о сложном». Весь материал снабжен подробными схемами, рисунками и комментариями. Вы на практике научитесь строить календарный план, создавать и назначать ресурсы, контролировать изменения проекта, выявлять и устранять риски, оптимизировать проект с целью достижения лучшего результата при неизменных условиях. Особая гордость – это описание воплощения реального проекта рекламной кампании торгового центра: начиная от постановки цели и заканчивая отслеживанием хода выполнения. Книга будет полезна абсолютно всем, кто хочет научиться эффективно организовывать свой рабочий процесс, добиваясь при этом больших результатов.

Книгу можно заказать по следующей ссылке: http://www.books.ru/shop/books/513576?partner=horrific


Учимся делать Autorun на Delphi Сегодня мы напишем авторан для CD/DVD дисков. Это тебе может пригодиться для сбора своего диска с софтом, с фильмами, с музыкой, да с чем угодно. Тем более, мы научимся запускать файлы и программы из Delphi-приложения. Это можно сделать несколькими способами. Но я покажу тебе только один - с помощью WinAPI, т.к. он более универсальный и очень лёгкий. Итак, создавай новый проект, как это делается я думаю, ты уже знаешь =) В разделе uses добавляй ShellAPI (именно в этом модуле находится функция). Кидай на фому три кнопки, ну и одну картинку для красоты. Назови кнопки вот так - "Установка", "Запуск", "ReadME". Создай для каждой из них обработчик события OnClick. Теперь доводи их до следующего этого вида: procedure TForm1.Button1Click(Sender: TObject); begin ShellExecute(Handle, nil, '/setup.txt', nil, nil, SW_SHOW); end; procedure TForm1.Button2Click(Sender: TObject); begin ShellExecute(Handle, nil, 'calc.exe', nil, nil, SW_SHOW); end; procedure TForm1.Button3Click(Sender: TObject); begin ShellExecute(Handle, nil, '/ReadME.txt', nil, nil, SW_SHOW); end; Давай разберёмся, в том, что здесь написано. Как ты, наверное, уже заметил, в каждом обработчике мы используем одну и ту же функцию ShellExecute. Что это за функция? Эта функция может открыть или напечатать определенный файл. Т.е. например, с файлами типа ".mp3" у тебя связан Winamp "winamp.exe" и запуск файла "cool.mp3" приведет к тому, что будет запущен Winamp и в него будет передан параметр с именем файла, т.е. открыт файл "cool.mp3", и начнётся его воспроизведение. Функция описана вот так: function ShellExecute(hWnd: HWND; Operation, FileName, Parameters,Directory: PChar; ShowCmd: Integer): HINST; Она имеет 6 параметров: 1. hWnd: Хендл родителя запускаемого приложения. Мы указали Handle, т.е. хендл своего приложения. 2. Operation: Строка, определяющая команду для исполнения. Может содержать: "open" - открыть файл определенный параметром FileName. "print" - напечатать файл определенный параметром FileName. "explore" - открыть папку определенную параметром FileName. У нас этот параметр равен nil, значит, по умолчанию выполнится операция "open".


3. FileName: Определяет имя файла или папки для открытия или печати. Вот этот параметр интересует нас больше всего. Здесь указываем, какой файл\папку\программу мы хотим открыть. Например, нажатием второй кнопки мы запускаем калькулятор. 4. Parameters: определяет параметры, передаваемые при запуске исполняемого приложения. При запуске документа его использовать не надо. Параметр может быть равен Nil, именно этому он у нас и равен. Потому что нам не надо каких-то дополнительных параметров. 5. Directory: определяет каталог по умолчанию (рабочий каталог). Получить\установить можно с помощью функций GetCurrentDirectory, SetCurrentDirectory. Если nil, то используется данный каталог. В нашем случае - это каталог, откуда запустилась наша прога. 6. ShowCmd: определяет режим открытия файла. Константа открытия\показа SW может быть равна: SW_HIDE - Прячет окно и переводит в активное состояние другое окно. SW_MINIMIZE - Минимизирует окно и активизирует окно верхнего уровня в списке менеджера окон. SW_RESTORE - Действует так же, как и SW_SHOWNORMAL. SW_SHOW - Активизирует окно и выводит его в текущей позиции и текущего размера. SW_SHOWDEFAULT - Активизирует окно и выводит его с использованием текущих умолчаний. SW_SHOWMAXIMIZED - Активизирует окно и выводит его с максимально размером. SW_SHOWMINIMIZED - Активизирует окно и выводит его в виде пиктограммы. SW_SHOWMINNOACTIVATE - Выводит окно как пиктограмму; бывшее активное в данный момент окно остается активным. SW_SHOWNA - Выводит окно с учетом его состояния в данный момент; активное в данный момент окно остается активным. SW_SHOWNOACTIVATE - Выводит окно в его прежней позиции и прежнего размера; активное в данный момент окно остается активным. SW_SHOWNORMAL - Активизирует окно и выводит его на экран. Если окно было увеличено или уменьшено до пиктограммы, то Windows восстановит начальное положение и размер окна. SW_SHOWSMOOTH - Выводит окно так, чтобы оно меньше всего перекрывалось с другими окнами. У нас эта константа равна SW_SHOW, потому что нам надо, чтобы фокус передался запускаемому окну. Функция ShellExecute возвращает хендл открытого приложения. Если возвращаемое значение меньше 32, значит, произошла ошибка. Вот основные возвращаемые ошибки: 0 - Системе не хватает памяти, выполняемый файл испорчен или нехватает ресурсов. ERROR_FILE_NOT_FOUND - файл не найден. ERROR_PATH_NOT_FOUND - путь не найден. ERROR_BAD_FORMAT - EXE-Файл неверен (не-Win32.EXE или ошибка в .EXE). SE_ERR_ACCESSDENIED - ОС отвергла доступ к файлу. SE_ERR_DLLNOTFOUND - динамическая библиотека(.DLL) не обнаружена. SE_ERR_FNF - файл не найден. SE_ERR_NOASSOC - нет приложения ассоциированного с данным типом файла. SE_ERR_OOM - недостаточно памяти для завершения операции. SE_ERR_PNF - путь не найден.


Ну, вот прога и готова, можешь поиграться с ней. Теперь давай сделаем так, чтобы она запускалась автоматически с диска. Для этого создавай файл "autorun.ini" со следующим содержанием: [autorun] open=autorun.exe icon=autorun.ico В блоке всего две строки: "open=autorun.exe" - эта строка показывает, что запустить, "icon=autorun.ico" - эта строка показывает, какую сделать иконку диска. Ну вот, и всё, авторан готов! Переименовывай прогу в "autorun.exe"и записывай на диск. Только не перепутай пути файлов! Прога и ини-файл должны лежать в корневом каталоге диска!

Исходники примера ищи в архиве autorun.rar by Васючков Андрей aka Soffric E-mail: soffrick@mail.ru WWW: http://LiveOfPC.3dn.ru


Dельфиний дZен №1 : Самомодифицирующийся код …ты городишь всякую чушь, откуда тебе самомодифицирующийся код на Delphi возьмется, это ж тебе не ассемблер… (реплика на форуме VR-Online) Итак, чтобы долго тут не размусоливать, приведу маленький, но вполне рабочий пример самомодификации кода на Delphi. Компилировался пример на D7 со всеми настройками проекта по умолчанию (в частности оптимизация была включена). На всякий случай к исходникам приложил и бинарник, потому что вполне возможно, что этот код у тебя сразу правильно не скомпилится. program Project1; {$APPTYPE CONSOLE} uses Windows; const offset = 32; // Смещение может быть и другое procedure Main; var i, x: Integer; OldPageProtection: Cardinal; base: PByte; begin x := 1; base := PByte(Cardinal(@Main) + offset); VirtualProtect(Pointer(base),1,PAGE_EXECUTE_READWRITE,OldPageProtection); for i := 1 to 5 do begin inc(x); base^ := base^ xor 8; // inc(x) <-> dec(x) end; VirtualProtect(Pointer(base),1,OldPageProtection,OldPageProtection); Write('x = '); WriteLn(x); Readln; end; begin Main; end.

Давай посмотрим, что же в этом коде такого необычного. На первый взгляд ничего особенного: крутится цикл и переменная Х увеличивается на единицу 5 раз (ну и еще какое-то шаманство непонятное J ). То есть на экран должно быть выведено: «x = 6». Но при запуске программа выводит строку «x = 2». Как же так, почему? Да потому, программа за время работы несколько раз модифицировала свой код! Вот с этого места можно начинать подробно. Для наших экспериментов ничего кроме стандартной среды Delphi не понадобится. Только сразу же надо освоить одну комбинацию горячих клавиш: ALT+CRTL+C. Это вызов окна CPU, то есть дизассемблера с отладчиком в одном флаконе. Кроме того, надо достать из закоулков памяти хоть какие-то знания по ассемблеру. А куда ж без него? Мы же машинный код модифицировать собрались. Да, еще нужен обычный виндовый калькулятор, если не умеешь в уме считать шестнадцатеричные числа.


Разберем программу по порядку. Танцевать будем от печки, то есть от begin. С инициацией переменной X все понятно. Затем устанавливается указатель base. Смотрим: base:= PByte(Cardinal(@Main) + offset); К адресу функции Main прибавляется какое-то смещение 32. Так на что устанавливается base? Запускаем пример на отладку: 3 раза жмем F7 и оказываемся в начале функции Main. Жмем ALT+CRTL+C:

У меня начало функции находится по адресу $00403A80 (у тебя может быть другой адрес). Прибавляем к адресу смещение 32, получается $403AA0. То есть base указывает на инструкцию с кодом 46, которая соответствует строчке «inc(x)» в коде. Запомни это. С

помощью

функции VirtualProtect мы разрешаем любые действия (флаг PAGE_EXECUTE_READWRITE) со страницей памяти, к которой принадлежит указатель base. Это для того, чтобы была возможность переписать исполняемый код, то есть собственно совершить самомодификацию. (Если VirtualProtect закомментировать, то программа после запуска тихо и быстро умирает, т.к. исполняемый код по умолчанию изменять запрещено) В конце листинга VirtualProtect вызывается снова, чтобы восстановить прежние атрибуты страницы памяти. Надо стараться быть аккуратным. Дальше начинается крутиться цикл. В нем переменная Х увеличивается на 1, это как бы понятно. А потом содержимое ячейки памяти по указателю base «проксоривается» с числом 8. А что у нас по этому указателю? Предыдущая строчка: «inc(x)»! Машинная инструкция $46 соответствует ассемблерной мнемонике INC ESI, но после модификации в этой ячейке памяти лежит $4E, которая соответствует мнемонике DEC ESI. Вот она самомодификация! На


следующем витке DEC ESI снова превращается в INC ESI. Получается, что на каждой итерации цикла Х не увеличивается постоянно, а то увеличивается, то уменьшается на 1. Т.к. число итераций нечетное, то последняя итерация увеличивает Х. Поэтому и получается на выходе 2, а не 6, как если бы самомодификации не было. Осталось только вывести результат на экран. Адрес изменяемой инструкции в этом коде вычисляется от адреса начала функции. То есть самомодифицирующийся код перед работой должен сначала определить свое местоположение. Для этого ему нужна отправная точка, якорь, зацепившись за который код вычисляет адреса изменяемых инструкций. В данном случае был использован указатель на начало функции, но может быть и другой подходящий (то есть близко расположенный) объект. Другой метод «обнаружения себя», основан на небольшом ассемблерном извращении. Сначала я его не использовал, т.к. хотел показать пример на чистом Паскале, но он очень распространен и фактически является стандартом. Поэтому если ты не испытываешь органической неприязни к асму, то должен обязательно узнать об этом методе. Это может выглядеть примерно так: ... asm call @label; @label: pop base; end; VirtualProtect(Pointer(base),1,PAGE_EXECUTE_READWRITE,OldPageProtection); for i := 1 to 5 do begin inc(x); PByte(base + offset)^ := PByte(base + offset)^ xor 8; end; VirtualProtect(Pointer(base),1,OldPageProtection,OldPageProtection); ...

Вся соль с мясом находятся между строчками asm и end; Инструкция call вызывает саму себя, а в качестве адреса возврата в стек заносится адрес следующей за call’ом инструкции. Вот этот-то адрес нам и нужен. Извлекаем его в переменную base с помощью pop и дело в шляпе. Остается только пересчитать offset. Это будет разность между адресом inc(x) и адресом pop base. Считаем как обычно в окне CPU:


Следует отметить, что эти методы самомодификации не способствуют переносимости кода, т.к. другая версия Delphi или с другими настройками компилятора вполне может создать совсем другой код. Тогда программа будет работать неправильно, т.к. offset окажется неправильным. В этом случае вычисляй offset сам: в окне CPU найди инструкцию, соответствующую inc(x) и вычти из ее адреса адрес начала функции. Как видишь в самомодифицирующемся коде нет ничего сложного и магического. И его реализация доступна не только ассемблерным гуру, но обычным Delphi-программерам. Хотя в обычных ситуациях потребность в самомодификации возникает крайне редко. Тогда зачем это нужно? Ну во первых это просто интересно. А вообще такой прием можно использовать в защитных механизмах (защите от хакеров, кракеров и проч.) При умном использовании он сильно усложнит задачу взломщику, хотя и имеет бооольшой недостаток: его легко идентифицировать по использованию функции VirtualProtect.

Продолжение следует … Исходники примера ищи в архиве mod_code.rar By Орехов Роман aka tripsin E-mail: tripsin@yandex.ru


Midlet Pascal: Работа с ресурсами: подключение и чтение Дарова, юный кодер! Сегодня мы с тобой опять будем кодить для своей мобилы. И опять будем писать на MidletPascal - чудо-юдо языке программирования для сотовых телефонов. И разговор у нас пойдёт не много не мало о ресурсах твоей проги. Почему? Постараюсь объяснить: Во-первых, это просто необходимо, это основы работы с языком программирования. Вовторых, это способ засунуть в твоё приложение музыку, картинки и прочий хлам (в одной из следующих статей я покажу тебе, как сделать что-то вроде плеера, ну или просто фоновой музыки для проги). В-третьих, это очень удобно. Рассмотрим пример: тебе надо написать шпоры на экзамен, потому что учить впадлу, а сдавать надо. :) На экзамене 40 биллетов. Просто так засунуть их в тело проги не хорошо. Потому что это не удобно - засоряется исходник; это долго - если в самом исходнике много лишних данных, то прога, естесственно, работать будет медленнее; да и просто это не этично. Надо сделать 40 аккуратных файлов с ответами на экзаменационные вопросы, засунуть их как ресурсы в прогу и спокойно работать с ними! По крайней мере, так ты не запутаешься в своём проекте - всё будет разложено по полочкам. Где и как? В папке с проектом есть каталог под названием "res". Там по дефолту храниться картинка - иконка проекта. Очень удобно держать все ресурсы в этом каталоге, потому что он специально для этого сделан. Но просто поместить туда файл и работать с ним не проканает. Надо сначала этот файл ресурсов подключить к нашему проекту. Делается это очень просто : выбирай в главном меню "Project->Import resource file..." Появится обычный диалог выбора файла. Выбирай нужный и дави "Открыть". Всё, ресурс в проекте, осталось тока написать прогу и скомпилировать её :))) Можно конечно, сделать для своего удобства отдельные каталоги под ресурсы, но меня устраивает стандартная папка "res". А как поступишь ты, решать тебе. Работа. Давай напишем маленький примерчик для работы с ресурсами. Создовай текстовый файл "data.txt", ну напиши там чё-нибудь, например, "Pupkin Zade RooleZZZ!!!!". Только напиши это не в одной строке, а в нескольких. Потом поймёшь почему. Подключай этот файл к проекту как ресурс, как это делать ты уже знаешь. Теперь в редакторе кода, измени сорец до вида: program Resourse; //Это название проги, пиши чё хочешь var data : resource;//наш ресурс str : string; index : integer; begin data := OpenResource('/data.txt'); //открытие ресурса if (resourceAvailable(data)) then //проверка begin str := ReadLine(data); //читаем строку CloseResource(data); //закрываем ресурс end; ShowForm; //создаём форму... index := FormAddString('Text :' + str); //выводим инфу из ресурса Delay(10000); //задержка end.


Давай разбираться! Строкой data:= OpenResource('/data.txt'); я инициализирую переменную data и открываю ресурс. Функция открытия имеет только один параметр - имя файла-ресурса. Этой строкой if (resourceAvailable(data)) then я проверяю, открылся ли ресурс? Т.е. функция ResourceAvailable(res: resource):boolean вернёт true если ресурс, указанный в параметре, открыт нормально. Дальше я присваиваю переменной str функцию ReadLine(res: resource):string, которая при нормальной работе возвращает строку файла-ресурса. Потом надо закрыть файл, делается это функцией CloseResource(res: resource). Ну а дальше, тебе должно быть всё понятно и без моих слов. Пробуй! Прога работает, она выводит первую строку созданного тобой файла. Помнишь, я просил тебя создать файл из нескольких строк? Так вот, помни, что функция ReadLine(res:resource):string выводит только одну строку. Чтобы написать больше строк, добавь ещё одну строковую переменную, например str2, и после str := ReadLine(data); добавь str2 := ReadLine(data);, а после index := FormAddString('Text :' + str); добавь следующее: index := FormAddString('Text_2 :' + str2);. теперь будут читаться две строки, и так далее... Ещё MidletPascal позволяет читать файл-ресурсов побайтно. Для этого надо завести переменную типа integer и использовать функцию ReadByte(res: resource):integer; Для удобства и уменьшения кода используй всевозможные циклы. Это упростит твою работу. Разбор полётов. Вот ты и научился работать с ресурсами. Ничего сложного здесь нет, они не кусаются 8-). Теперь данные в твоей проге будут удобно скомпонованы, не будет свалки. Этого достаточно, чтобы переходить к следующим урокам. Надеюсь, тебе было интересно. Остальное позже...

by Васючков Андрей aka Soffric E-mail: soffrick@mail.ru WWW: http://LiveOfPC.3dn.ru


Midlet Pascal: Фоновая музыка твоего приложения. Итак, наступило время немного поработать с музыкой на твоей мобиле. Конечно, это не будет плеер с каталогизацией альбомов и прочими наворотами. Мы просто попробуем понять работу с музыкой на языке MidletPascal. Поняв этот материал, ты сможешь оснастить свою прогу фоновой музыкой. А если речь зайдёт за игры, то тут без музыки и звуковых эффектов никуда! Какая же игра, даже мобильная, без музыки? Вот, я тоже так думаю, так что не будем терять время и начнём! В одной из прошлых статей я рассказывал тебе про работу с ресурсами. Для понятия и реализации этого материала просто необходимо знать работу с файлами ресурсов. Если ты пропустил прошлую статью, то надо отыскать её и внимательно прочитать, иначе ничего не получится. Для работы с музыкой и звуками в MidletPAscal предусмотрено пять функций, которые мы с тобой сейчас и разберём: function OpenPlayer(resource:string; mimetype:string):boolean; Эта функция открывает указанный в первом параметре файл ресурса в аудио плеере. Функция вернёт false, если не сможет открыть файл. Ресурс не запустится на проигрывание, пока не будет выполнена функция 'startPlayer' (о ней немного позже). Второй параметр может принимать одно из следующих значений: • • • •

audio/x-wav - для проигрывания wav-файлов audio/basic - для проигрывания au-файлов audio/mpeg - для проигрывания mp3-файлов audio/midi - для проигрывания MIDI-файлов

Внимательно следи за поддерживаемыми форматами музыки. А то не хорошо получится, если твоя прога работает с mp3, а телефон клиента поддерживает только wav. Так же разработчики МидлетПаскаля предупреждают, что музыкальные функции работают только на телефонах MIDP-2.0, а на более ранних версиях телефонов приложения, использующие эти функции, приуд к краху. function SetPlayerCount(loopCount:integer):Boolean Эта функция устанавливает количество раз, которое музыка должна проиграться. Если значение 'loopCount' равно -1, то музыка будет проигрываться постоянно, т.е. повторяться. Функция должна быть использована после OpenPlayer, но перед startPlayer. function StartPlayer:boolean; Функция начинает проигрывать файл, открытый функцией OpenPlayer. Она возвратит false, если не сможет начать воспроизведение, а если всё в норме, то вернёт true. function GetPlayerDuration:integer; Возвращает продолжительность музыки (в миллисекундах), воспроизводимую плеером. procedure StopPlayer;


Останавливает музыку. Вот все инструменты, которые предлагает МидлетПаскаль для работы с музыкой. Их, конечно, мало, но всё же они есть. Так что будем пользоваться тем, что есть. Вот небольшой пример, который проигрывает мелодию: begin OpenPlayer('/simple.mid', 'audio/midi'); SetPlayerCount(-1); StartPlayer; Delay(5000); end. Здечь много не хватает, например, проверки правильной работы каждой функции. Но это уже твоё дело, моё дело - только показать тебе основы, а дальше сам разбирайся, улучшай и пробуй! Можно вывести форму, установить на ней в текстовом поле длину музыки, название файла и прочее, прочее... Пока хватит твоей фантазии. Ну а пока разбирайся со всем прочитанным, желаю удачи! Остальное позже...

by Васючков Андрей aka Soffric E-mail: soffrick@mail.ru WWW: http://LiveOfPC.3dn.ru


Фоновая музыка в Delphi приложениях Привет! Настало время разобраться с фоновой музыкой в Delphi-приложениях. Ты, наверно, сразу скажешь: "Можно просто взять TMediaPlayer и сделать его невидимым". Можно. Я не спорю. Но так прога будет кушать больше ресурсов компьютера. Да и звуковые файлы отдельно от EXE-шника таскать не удобно: могут изменить или вообще удалить. Сегодня я покажу тебе, как запихать WAV-файлы в EXE-шник и как воспроизвести их без компонента MediaPlayer. Если ты готов, то создавай новый проект и сразу сохраняй его в отдельную директорию.

Ресурсы Начнём мы с файла-ресурсов. Подбери какие-нибудь звуки. Я назвал их Sound01 и Sound02. Теперь создавай в директории своего проекта текстовый файл со следующим содержанием: • •

Sound01 RCDATA LOADONCALL Sound01.wav Sound02 RCDATA LOADONCALL Sound02.wav

Как ты наверно уже понял, это названия и пути к файлам. У нас файлы храниться прямо в каталоге с ресурсом, так что путь указывать необязательно, а достаточно просто указать имя. В данном примере два файла - Sound01 и Sound02. Но ты можешь запихать их туда сколько угодно, хоть альбом своего любимого певца :) Теперь переименовывай наш текстовый файл в "Sound.rc". Так. Теперь надо всё это дело скомпилировать. Компилить ресурсы будем через BRCC32.exe (Borland Resource CommandLine Compiler), который идёт в стандартной поставке Delphi. В качестве параметра нужно указывать путь и имя файла ресурсов. В нашем случае нужно выполнить строку: C:\-директория Delphi-\BIN\BRCC32.EXE C:\-директория проекта\Sound.rc В случае удачного стечения обстоятельств (ого, как завернул!), в смысле если всё прошло нормально, то в каталоге нашей программы появиться файл "Sound.res". Теперь его надо привязать к нашему проекту...

Кодим Итак, файл ресурсов создан. Теперь, в проекте после строки {$R *.dfm} нужно добавить ещё строку {$R Sound.res} . Этими действиями мы линкуем (привязываем) наш файл ресурсов к EXE-шнику, т.е. при компиляции проекта все файлы из ресурса присоединяться к телу программы. Теперь начнём работу с этими ресурсами. Сначала добавь в раздел uses ещё один модуль MMSystem, он нам пригодится для воспроизведения WAV-файлов. Давай сначала напишем процедуру воспроизведения файла из ресурсов. У меня она получилась вот такой: procedure PlayWAVfromRES(name:PAnsiChar); var hResource: THandle;


pData: Pointer;//указатель на ресурс begin hResource:=LoadResource( hInstance, FindResource(hInstance, name, RT_RCDATA)); pData := LockResource(hResource); SndPlaySound(pData, SND_MEMORY); FreeResource(hResource); end; Здесь всё просто: сначала получаем хендл ресурса при помощи функции LoadResource. В качестве второго параметра, передаваемого этой функции, идёт результат функции FindResource. Эта функция отыскивает, нужный ресур по его имени (в данном случае это переменная name). Потом указываем нашему указателю на ресурс. А при помощи SndPlaySound(pData, SND_MEMORY); мы проигрываем полученный файл. Ну а последней строкой мы освобождаем файл-ресурс из памяти. Теперь добавь к форме кнопочку и создай обработчик нажатия этой кнопки. Впиши в него следующее: PlayWAVfromRES('Sound01'); Это вызов нашей процедуры. В качестве единственного параметра указываем имя ресурса. Теперь можно сделать, чтобы ещё один звук проигрывался при открытии приложения. Для этого создай обработчик события OnCreate нашей формы и впиши туда: PlayWAVfromRES('Sound02'); Думаю, ты и сам понял, что здесь написано. Ну, вроде всё! Исходники моего проекта забирай здесь. Остальное позже...

by Васючков Андрей aka Soffric E-mail: soffrick@mail.ru WWW: http://LiveOfPC.3dn.ru


Внедрение DLL в чужое адресное пространство. Внедрение DLL в чужой процесс обычно требуется, если надо выполнить некоторый код с более высокими привилегиями или для изменения логики стороннего приложения, например, можно захватить таблицу импорта (IAT) и подменять результаты некоторых функций, как следствие возможно скрывать файлы, ключи реестра, процессы и делать всё на что хватит фантазии, от имени процесса, в который внедрена твоя DLL. Итак, существует три основных способа внедрения DLL в адресное пространство процесса в режиме пользователя (в 3-ем кольце): внедрение с помощью реестра, удалённых потоков и глобальных ловушек (hooks). Для демонстрации внедрения используем простенькую библиотеку ShowMe.dll, которая при каждой загрузке будет делать запись в скрытый, «системный», текстовый файл C:\Temp\Inject.dll library ShowMe; uses Windows; function IntToStr(val: dword): string; begin Str(val, result); end; procedure WriteLabelInFile(); var fH, bwr: dword; text: pchar; begin text:= PChar('Our DLL is now loaded in process:'#13+ 'PID = '+IntToStr(GetCurrentProcessId)+#13+ParamStr(0)+ #13'------------------------------------------------'#13); fH:=CreateFile('C:\Temp\Inject.dll', GENERIC_READ or GENERIC_WRITE, 0, nil, OPEN_ALWAYS, FILE_ATTRIBUTE_SYSTEM or FILE_ATTRIBUTE_HIDDEN, 0); SetFilePointer(fH, 0, nil, FILE_END); WriteFile(fH, pointer(text)^, Length(text), bwr, nil); CloseHandle(fH); end; begin try WriteLabelInFile(); except end; end.

Внедрение DLL с помощью реестра Данный способ работает только в Windows NT (2000, XP, 2003, etc.). При загрузке любого приложения, использующего user32.dll, а таких абсолютное большинство, все DLL, перечисленные в ключе реестра HKLM\Software\Microsoft\Windows NT\CurrentVersion\Windows\AppInit_DLLs, загружаются в


адресное пространство этого приложения. Например, следующая простенькая программа пропишет нашу тестовую dll в нужном ключе реестра. program LoadDLLviaRegistry; uses Windows; const my_dll = 'showme.dll'; var reg: HKEY; status: integer; sval: PChar; curr_val: array[0..255] of char; curr_len, curr_type: dword; begin status:= RegOpenKeyEx(HKEY_LOCAL_MACHINE, 'Software\Microsoft\Windows NT\CurrentVersion\Windows', 0, KEY_ALL_ACCESS, reg); if (status = ERROR_SUCCESS) then begin sval:=my_dll; curr_len:=255; RegQueryValueEx(reg, 'AppInit_DLLs', nil, @curr_type, @curr_val, @curr_len); curr_val[curr_len]:=#0; if (curr_val[0]<>#0)and(curr_val<>my_dll) then sval:=PChar(sval+' '+curr_val); RegSetValueEx(reg, 'AppInit_DLLs', 0, REG_SZ, sval, Length(sval)); end; end. После запуска этой программы все приложения, использующие user32.dll, получат заодно и нашу dll в своё адресное пространство. После перезагрузки код помещённый в ShowMe.dll сможет выполняться даже от системной учётной записи. Однако этот способ самый простой не только в реализации, но и в обнаружении, поэтому воспользоваться им в реальных задачах проблематично.

Внедрение DLL с помощью удалённого потока Наиболее сложным в реализации, но при этом наиболее гибким и мощным методом внедрения DLL является использование удалённых потоков. Для создания удалённого потока применяется API-функция CreateRemoteThread, она принимает 7 параметров, но нас интересуют только 3 из них. Первый параметр – это дескриптор процесса, в котором мы хотим создать поток, его возвращает функция OpenProcess (в нашем случае нужно открыть процесс с заданным идентификатором (PID) на запись). hProc:= OpenProcess(PROCESS_ALL_ACCESS or PROCESS_VM_WRITE,true,PID);

В качестве четвёртого параметра необходимо передать адрес функции LoadLibrary в целевом процессе, но если он импортирует функции из библиотеки kernel32.dll, то мы можем воспользоваться адресом LoadLibrary в нашем процессе, так как библиотека kernel32.dll в


целевом процессе расположена под тем же самым виртуальном адресом, что и в процессе, выполняющем внедрение (спасибо Microsoft`y за этот факт). Итак, чтобы получить нужный адрес применим функцию GetProcAddress: lpStartAddress:= GetProcAddress(GetModuleHandle('kernel32.dll'), 'LoadLibraryA'); Третий и шестой параметры установим в 0, а второй – в nil, седьмой параметр является выходным (out), в него запишется идентификатор созданного потока. Остаётся самый важный параметр – пятый, в него необходимо передать адрес аргумента, который будет передан функции LoadLibrary. Но тут есть большая проблема – нельзя просто передать сюда указатель на строку, содержащую имя нашей dll, так как строка физически находится в адресном пространстве процесса, выполняющего внедрение, поэтому этот указатель не имеет смысла для целевого процесса. Однако Microsoft опять спешит нам на помощь, предлагая способ записи данных в адресное пространство чужого процесса. Для этого необходимо выделить память в адресном пространстве целевого процесса, при помощи функции VirtualAllocEx, а затем записать в выделенную память имя DLL функцией WriteProcessMemory. Первым параметром функции VirtualAllocEx необходимо передать уже знакомый нам дескриптор целевого процесса, открытого на запись. Вторым параметром идёт адрес выделяемого участка памяти, но так мы не хотим повредить сам процесс, то предоставим вычисление этого адреса операционной системе, передав nil в этот параметр. Третьим параметром передаётся объём выделяемой памяти, под имя DLL нам вполне хватит 255 байт. Четвертый параметр – это тип выделения памяти, используем MEM_COMMIT, в этом случае память реально выделится и очистится от мусора. Последний параметр – это уровень защищенности выделяемой памяти, здесь можно запретить кеширование и даже доступ к выделенной памяти, но мы просто укажем, что мы хотим получить к ней доступ на чтение и запись, в итоге получается: pmem:= VirtualAllocEx(hProc, nil, 255, MEM_COMMIT, PAGE_READWRITE); Функция записи WriteProcessMemory весьма проста, ей надо передать дескриптор целевого процесса, открытого на запись, адрес участка памяти, куда будет произведена запись, полученный с помощью VirtualAllocEx, записываемые данные и их размер, а в качестве последнего параметра – переменную, в которую запишется число реально записанных байт. WriteProcessMemory(hProc, pmem, DllName, Length(DllName), wrb) Теперь всё готово для создания удалённого потока. Вот рабочий прототип программы внедрения: program LoadDLL; uses Windows, Tlhelp32, SysUtils; ... function Load_DLL(DllName: PChar; PID: dword): boolean; var hProc: dword; lpStartAddress, pmem: pointer; wrb, lpThId: dword; begin


hProc:=OpenProcess(PROCESS_ALL_ACCESS or PROCESS_VM_WRITE,true,PID); if (hProc<>0) then begin lpStartAddress:= GetProcAddress(GetModuleHandle('kernel32.dll'), 'LoadLibraryA'); pmem:= VirtualAllocEx(hProc,nil, 255, MEM_COMMIT, PAGE_READWRITE); if (pmem=nil) then result:=false else begin if not WriteProcessMemory(hProc,pmem,DllName,Length(DllName),wrb) then result:=false else if (dword(Length(DllName)) = wrb) then begin result:=CreateRemoteThread(hProc, nil, 0, lpStartAddress, pmem, 0, lpThId)<>0; end else result:=false; end; end else result:=false; end; const DllName = 'ShowMeHook.dll'; var PID: dword; begin SetSeDebugPrivilege; PID:=GetPID('notepad.exe'); if (PID>0) then Load_DLL(DllName, PID); end. Принцип действия функции Load_DLL я подробно описал выше, тёмными лошадками остаются функции GetPID и SetSeDebugPrivilege. Фунция GetPID просто ищет процесс с заданным именем и возвращает его идентификатор – PID. Перечисление процессов очень подробно рассматривал tripsin в выпуске VR-prog14, поэтому я не буду останавливаться на этом моменте. Функция SetSeDebugPrivilege стандартным образом получает отладочные привилегии (SeDebugPrivilege), это необходимо для получения возможности выделения памяти в системных процессах, таким образом ты можешь выполнять свой код от имени svchost.exe или winlogon.exe, а это уже реально круто. В прилагаемом к статье архиве ты найдёшь полную версию программы, с дополнительной функцией – внедрение DLL во все активные процессы.

Внедрение DLL с помощью глобальных ловушек Приложения получают массу сообщений, уведомляющих о различных событиях, например, нажатие на клавишу, когда одно из окон приложения активно. Microsoft предложила способ, позволяющий перехватывать оконные сообщения чужого процесса. Именно его мы и используем для внедрения DLL в чужое адресное пространство. Заключается этот способ в вызове функции SetWindowsHookEx, которой передаётся 4 параметра: первый – тип перехватываемых сообщения (WH_KEYBOARD для перехвата сообщений от клавиатуры), второй – указатель на процедуру-обработчик (именно она может стать сердцем кейлогера, например), третий – виртуальный адрес библиотеки, которая содержит процедуру-обработчик и


последний параметр – это идентификатор потока, который мы хотим захватить, либо 0 для захвата всех потоков. Внесём некоторые изменения в нашу внедряемую библиотеку, соответствующие глобальному перехвату: ... var KHook: HHOOK; ... function KProc(Code: integer; wParam: Word; lParam: LongInt): LongInt; stdcall; begin result:=CallNextHookEx(KHook, code, wParam, lParam); end; ... procedure Run(); begin while true do Wait(100); // ожидание 100 мс. end; ... var thID: dword; begin KHook:=SetWindowsHookEx(WH_KEYBOARD, @KProc, HInstance, 0); WriteLabelInFile(); end. Здесь KProc – процедура-перехватчик, она будет вызываться при каждом нажатии клавиши на клавиатуре. В данном случае она просто передаёт управление следующему обработчику, но никто не мешает тебе записать это нажатие в файл и сделать кейлогер, написание кейлогера выходит за рамки данной статьи, но если эта тема будет кому-нибудь интересна, то я освещу её в одной из последующих статей... Для внедрения теперь достаточно вызвать rundll32.exe ShowMeHook.dll, Run. После первого же нажатия в окне чужого приложения, rundll32.exe больше не нужен, достаточно одного процесса с внедрённой DLL, чтобы контролировать всю систему. Все зараженные процессы отметятся в C:\Temp\Inject.dll.

P.S. Теперь ты знаешь как можно выполнить произвольный код от имени любого процесса, но есть один небольшой недостаток – все три способа вызывают предупреждения фаерволла (проверялось на Outpost), хотя понять их смысл сможет только программист (да и то далеко не каждый придаст им соответствующее значение), поэтому если ты обернёшь свою программу в красивую обёртку, пользователя не остановят никакие предупреждения и он разрешит ей всё, ибо социальную инженерию ещё никто не отменял... Исходники примера ищи в архиве DLL_inject.zip (+ рабочий кейлогер)

By: Romul aka Смирнов Роман E-Mail: romul@vr-online.ru


Введение в защиту Shareware-программ. Сначала я хотел написать конкретную Shareware-защиту и описать принцип её действия, но вскоре понял, что любая опубликованная защита априори бесполезна, поэтому в данной статье я опишу основные принципы защиты Shareware-программ, которые позволят усложнить взлом программы, а после прочтения статьи ты сможешь сам организовать достойную защиту. Только не строй иллюзий, защиту, которую нельзя взломать ещё никто не смог написать, даже компании с огромным бюджетом и штатом специалистов в данной области. Поэтому если ты написал что-нибудь стоящее, это всё равно взломают, вопрос только в том сколько времени на это уйдёт. Как говорится, если для Вашей Shareware-программы нет крека, значит ей просто никто не пользуется, ну или почти никто (кол-во пользователей в таком случае совпадает с колвом людей, которые заплатили тебе за регистрацию). Этот факт хорошо известен авторам популярных программ, например, WinRAR, сложно найти более популярную программу русского автора и при этом более простую во взломе, «зарегистрироваться» можно заменой пары байт, но даже если у тебя нет дизассемблера, то достаточно удалить наг-скрин (диалог, который напоминает о том, что это оценочная версия, и не плохо было бы купить лицензию ;-)) из ресурсов (диалог REMINDER) в редакторе ресурсов (ResHacker, Restorator,etc) и там же поправить заголовок, и больше ничто не будет напоминать тебе о регистрации. Почему же Александр Рошал не напишет более сложную защиту? Всё просто, он прекрасно понимает, что его программа настолько популярна, что даже если он потратит кучу времени на написание сложной системы регистрации, её всё равно взломают в первую же неделю после выхода, если не в первый же день ;-). Так вот будет ли пользователь пользоваться креком зависит от его менталитета, цены программы и наличия бесплатной техподдержки с твоей стороны в случае регистрации (также могут быть бесплатные обновления). Поэтому 7 раз подумай, нужна ли тебе сложная защита, прежде чем однажды начать её писать. Но если ты всё-таки решил усложнить процедуру взлома, то читай дальше. Для начала запомни несколько базовых принципов: •

не привлекай внимания к ядру защиты, не надо выводить всякие дурацкие сообщения типа «Спасибо за регистрацию» или «Неверный пароль», это существенно упростит взлом, особенно если ты их выводишь сразу после проверки регистрационных данных, оптимальным вариантом будет отложенная регистрация, т.е. не проверяй регистрационные данные сразу после ввода, просто запомни их и выведи нейтральное сообщение типа «Для завершения регистрации необходим перезапуск программы / перезагрузка компьютера.». Корректность регистрации проверяй при закрытии (OnCloseQuery), либо при открытии программы (OnShow), если данные корректны, то просто сохрани зарегистрированное состояние, как именно – другой вопрос. Например, можно создать файл с зашифрованной регистрационной информацией, как это делает DrWeb, или создать десяток ключей в реестре, а комбинация из значений некоторых из них будет соответствовать зарегистрированному состоянию, а значения других будут случайными. Одного ключа недостаточно, т.к. его легко обнаружить(например, с помощью утилиты RegMon) и перевести в нужное состояние. не сравнивай сами пароли, это упростит написание кейгена. Вместо этого следует сравнивать их хеши, полученные при помощи какого-нибудь необратимого алгоритма шифрования (MD5, SHA1, etc). Если ты решил сделать паролем хеш логина, то не забудь использовать шифрование хотя бы несколько раз, при этом попутно выполнять произвольные операции (xor, циклический сдвиг, урезание, замену подстрок, etc.) над


строкой текущего хеша, не помешает также сделать паролям привязку к дате, чтобы они со временем устаревали. используй exe-упаковщики и протекторы. Кроме уменьшения размера исполняемого файла упаковщики дадут дополнительный бонус твоей системе защиты, так как крекеру, прежде чем перейти к взлому, необходимо распаковать твою программу, поэтому старайся использовать упаковщики, для которых в интернете нет автоматических распаковщиков, уже это отпугнёт многих крекеров. Но не обольщайся, это не даст тебе гарантию от взлома, так как упаковщик добавляет в файл и распаковщик, чтобы перед выполнением программа могла распаковаться в оперативную память, вот тут то её и можно взять тёпленькой и беззащитной, останется только восстановить таблицу импорта, например, при помощи Import REConstructor. Большую защиту тебе дадут протекторы, такие как Armadillo, их защиту весьма трудно снять..

Теперь рассмотрим как взламывают программы, если ты думал, что взлом осуществляется только силой мысли и ручной правкой экзешника в блокноте, то это в корне неверно. Для взлома применяется целая армия специальных утилит: • • • •

декомпиляторы дизассемблеры отладчики вспомогательные утилиты (редакторы PE-заголовков, секций, ресурсов; утилиты мониторинга файловой системы и реестра, утилиты слежения за окнами и вызовами APIфункций; HEX-редакторы; автоматические распаковщики; etc.)

Декомпиляторы частично или полностью воссоздают программу на языке высокого уровня, используя лишь исполняемый файл. Декомпиляторы существуют для множества компиляторов: Delphi, .NET, VB, Java, etc. Нас в данный момент интересует Delphi (для C++Builder будет тоже самое), для него есть два декомпилятора: DeDe и exe2dpr. exe2dpr обладает лишь базовыми функциями, которых, впрочем, для взлома часто бывает достаточно: восстановление всех форм проекта и всех обработчиков событий с их адресами. DeDe кроме этих функций предоставляет встроенный дизассемблер и удобный интерфейс для исследования полученных данных, также он найдёт все используемые классы и модули, распознает VCL процедуры и ещё много чего... Защититься от декомпилятора, если крекер уже снял упаковщики и протекторы, практически невозможно, поэтому ни в коем случае не помещай проверку регистрационных данных в обработчик OnClick той самой кнопки ОК, которая подтверждает их ввод, - это начало конца твоей защиты, помни об этом! Но не всё так страшно, декомпиляция программ, написанных на Delphi является частичной, поэтому её мало для взлома, после неё необходимо воспользоваться дизассемблером и/или отладчиком. А радикальным средством от декомпиляторов будет написание программы на Visual C++, для него насколько мне известно декомпилятора не существует (правда существуют сигнатуры для IDA, которые позволят распознать все библиотечные функции). Дизассемблеры переводят исполняемый файл на язык ассемблера, а также распознают таблицу импорта, строки и ресурсы. Некоторые дизассемблеры включают в себя отладчики. Для защиты от дизассемблера нужно применять шифрование строк и функций, подробнее об этом можно прочитать в статьях tripsin`a в этом и предыдущем номерах журнала. Программы на Delphi слабо используют ресурсы (по сравнению с программами на Visual C++), поэтому хватит шифрования строк, регистрационных и антиотладочных функций.


Отладчики позволяют пошагово выполнять программу, поэтому от них возможно избавляться во время выполнения, при запуске программы надо вызвать зашифрованную функцию, которая прервёт процессы наиболее популярных отладчиков, если они запущены. А т.к. большинство отладчиков позволяет отлаживать уже запущенную программу, то необходимо вызвать ту же функцию и перед проверкой регистрационных данных. Наиболее популярными отладчиками являются OllyDbg, отладчик, встроенный в IDA и SoftICE. Первые два являются отладчиками уровня пользователя, поэтому для несложной защиты достаточно просто прервать их процессы: OllyDbg.exe, idag.exe, idag64.exe. Это можно сделать, например, такой функцией: uses Tlhelp32; ... function KillTask(ExeFileName: string): integer; const PROCESS_TERMINATE=$0001; var ContinueLoop: BOOL; FSnapshotHandle: THandle; FProcessEntry32: TProcessEntry32; begin result := 0; FSnapshotHandle := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); FProcessEntry32.dwSize := Sizeof(FProcessEntry32); ContinueLoop := Process32First(FSnapshotHandle, FprocessEntry32); while integer(ContinueLoop) <> 0 do begin if ((UpperCase(ExtractFileName(FProcessEntry32.szExeFile)) = UpperCase(ExeFileName)) or (UpperCase(FProcessEntry32.szExeFile) = UpperCase(ExeFileName))) then result := Integer(TerminateProcess(OpenProcess( PROCESS_TERMINATE, BOOL(0), FProcessEntry32.th32ProcessID), 0)); ContinueLoop := Process32Next(FSnapshotHandle, FProcessEntry32); end; CloseHandle(FSnapshotHandle); end; Для остановки SoftICE, если он запускается как служба (наиболее частый случай), надо выполнить команду оболочки net stop ntice, например так: uses ShellAPI; ... ShellExecute(0, 'open', 'net', 'stop ntice', nil, SW_HIDE);


Теперь ты знаешь базовые принципы взлома и защиты, так что можешь приступать к написанию защиты, которая сделает взлом твоей программы длительным и увлекательным занятием для крекера.

By: Romul aka Смирнов Роман E-Mail: romul@vr-online.ru


Атака на Delphi-приложения Если вы пишите на делфи и считаете, что ваше творение есть черный ящик – то сейчас мне придется вас переубедить. Цель данной статьи показать читателю на конкретных примерах, какие инструменты использует крякер при атаке не только на делфи, но на windowsприложения вообще. В качестве примера будет рассмотрен популярный Everest Ultimate, написанный как раз на делфи.

Его нам нужно избавить от триальности. Я не собираюсь описывать конкретно взлом, а покажу его с другой стороны. Со стороны используемых для этого программ.

Разведка Итак, первое, с чего необходимо начать – узнать, на чем написана программа и не запакован ли исполняемый файл. Для этого подойдет замечательнейшая программа PEiD.


Как видно, наша ласточка запакована UPX'ом. Провести анпакинг – дело пяти секунд. Лично я воспользуюсь UPX Shell, хотя можно взять Quick Unpack (универсальная утилита, распаковывающая простые пакеры – ASPack, UPX, PE-Pack и т.д.) или соответствующий плагин к пейду.

Помимо упаковки, исполняемый файл может быть защищен протектором (например, ASProtect, Armadillo и т.д.). На каждый из них есть свое лекарство, однако процесс восстановления более сложен и здесь нажатием одной кнопки не обойдешься. Вам нужно только знать, что против профессионала никакой протектор не спасет. Итак, получив на выходе готовенький экзешник, мы снова запускаем пейд, чтобы на этот раз узнать, на чем написано исследуемое творение. «Borland Delphi 6.0 - 7.0» - то что нужно. Перед тем как запустить отладчик, мы значительно упростим себе задачу, восстановив исходный код программы.


Восстановление исходного кода Для этого нам потребуется утилита EMS Source Rescuer.


Повезло. Разработчик не использовал сторонних компонентов, поэтому проект открылся легко и непринужденно. Да даже если бы и не вышло – все равно наша задача не пересобрать приложение, вставив лейбл «здесь был я», а найти окно регистрации.

Проверка на валидность происходит в событии onChange – опытный делфист поймет это с первого взгляда. Собственно, что нам дало восстановление? Ведь вместо кода, мы видим какой-то адрес. Ради этого все и затевалось. Чтобы не ковыряться в отладчике, ища необходимую процедуру, мы сходу определили цель. Сэкономлено несколько минут. К тому же, полезно знать, какие компоненты использовал программист, при написании приложения.


В дальнейшем начинается отладка, итогом которой будет или бит-хак, или кейген.

Ресурсы Лишить ресурсов проще простого. Для этого необходимо воспользоваться редактором ресурсов. Лично мне очень нравится тот, что идет вместе с PE Explorer.


Так же не могу не сказать о ResHacker. Сейчас утилита сдает позиции, но она по прежнему на рабочем столе - люблю маленькие и реактивные программы. Кстати, чтобы извлечь картинки из компонентов, не используя принт-скрин, достаточно копирнуть нужный текст и вставить его в dfm.

Декомпиляция DeDe - это уже посерьезнее, чем EMS. Данный инструмент значительно облегчает отладку, анализируя код. Пока вам достаточно просто знать, что такая программа существует.

Криптография


Довольно часто крэкеру приходится иметь дело с программами, шифрующими какие-то данные. Это может быть серийник, данные в файле и т.п. Полезно знать, какие популярные алгоритмы включил в свою программу разработчик. Crypto searcher прекрасно справляется со своей работой.

Если среди найденных алгоритмов будет MD5, необходимо обзавестись MD5Inside. Возможно, с ее помощью удастся решить некоторые проблемы.

Заключение


Рассмотренные мною программы должны быть не то что у каждого крэкера – у каждого уважающего себя программиста. Каждой из них найдется применение, даже если вы не обладаете соответствующими знаниями. Хотя я могу определять с довольно высокой точностью на чем написана программа, за меня это в мгновение сделает пейд. Зная, какие компоненты использует конкурент, можно будет создать аналог или сделать также красиво и функционально. При использовании в проекте алгоритма AES, стоит судить о довольно высокой квалификации разработчика (по крайне мере с криптографией он знаком). Если же наоборот, данные шифруются непонятно как, можно попробовать реверсировать алгоритм. Наверняка применен примитивный XOR или какойнибудь кривой самопал. Напоследок хочется еще раз напомнить, что если вашу программу захотят сломать – это наверняка сделают. И тут уже ни протекторы, ни методы противодействия отладке помочь не в состоянии. В стороне остались многие программы, интересные не только крякерам, но и простым смертным. Например, программы снятия дампа, winapi – шпионы, тулзы работы с окнами и элементами управления. Я уже не говорю про дизассемблеры, отладчики, приложения восстановления импорта, мониторы реестра и файлов. Но об этом как-нибудь в другой раз.

By: Коновалов Сергей 2007 E-mail: Sergio_Lightning@yahoo.com


Ribbon в Delphi С появлением Windows Vista, а затем Office 2007, многие программисты почувствовали себя неуверенно. Оно и понятно – очень много изменений, особенно в интерфейсе. Одним из них стал Ribbon. Лента (в переводе именно это означает Риббон) заменила собой привычные тулбары. Более эргономичная, более удобная, более красивая. Имеет также ряд недостатков, о которых будет сказано ниже. Собственно речь пойдет вот об этом:

Одни из крупнейших производителей компонентов, такие как TMS и Developer Express уже представили на суд публике свои реализации риббона. Если первые сделали это еще осенью, то вторые – только в декабре. Лично я очень не люблю компоненты TMS. Может конечный пользователь и не заметит разницы, но по удобству использования в режиме проектирования они безоговорочно проигрывают ExpressBars. По этой причине будут рассмотрены только DevExoress’ы.

Компоненты и возможности В поставку ExpressBars 6 beta 2 входит демка редактора:


Все, что показано на скриншоте, будет доступно разработчику: начиная от самого Риббона и заканчивая шкуркой окна. На вкладке ExpressBars появилось 5 новых компонентов. На форме все выглядит так:

1 - Ribbon 2 - Status bar 3 - Application menu 4 - Popup в соответствующем стиле 5 -Хранилище визуальных подсказок (screen tip)

Ribbon


Не смотря на самостоятельность, управляется это добро с помощью BarManager. Риббон является своеобразным контейнером для созданных тулбаров. Создавая панели инструментов, мы привязываем их с помощью свойства к одной из нами созданных групп(RibbonGroup). Причем каждая группа принадлежит одной из вкладок (RibbonTab). Таким образом, мы можем разместить несколько панелей инструментов на одной вкладке. Управлять всем этим одно удовольствие, в отличии от тмс.

Quick access toolbar – панель инструментов быстрого доступа. В зависимости от размещения панели (за это отвечает QuickAccessToolbar->Position), изменяется и вид ленты. По умолчанию QAT располагается под, но если в свойстве указать AboveRibbon, внешний вид Риббона преобразится: появится стильная иконка (1), являющаяся не просто декоративным украшением, а предназначенная для вызова особого меню (об этом чуть ниже). Сама же панель(2) также будет выглядеть по-другому:


Application menu То самое меню, что вызывается при нажатии на большую круглую кнопку. Является частичным аналогом пункта «файл». За нее отвечает компонент dxBarApplicationMenu.

Не нужно обладать особым талантом, чтобы совершить двойной щелчок мышью и перетащить в окно элементы из менеджера баров - все предельно просто.

Screen tip Новая, более наглядная подсказка. За счет добавления изображения, можно еще проще донести до пользователя назначение контролла. Необходимо заметить, что размер картинки жестко ограничен. Показанный выше пример – предел (изображение с большими размерами будет сжато до необходимого). Картинка не является обязательным атрибутом. Просто текст, кстати, тоже выглядит неплохо.


Стили 3 цветовые схемы: голубая, серебряная и черная. К сожалению, если вы решитесь использовать ленту в своих проектах, придется искать остальные элементы управления в соответствующем стиле. Иначе получится колхоз. Кстати, возможности добавлять новые шкурки - нет (может оно и к лучшему). Чтобы «одеть» окно в другую шкурку, необходимо помимо указания в соответствующем свойстве(SupportNonClienDrawing), изменить родителя формы на TdxCustomRibbonForm, подключив модуль dxRibbonForm.

Status bar Ничего особого. Натравите статус бар через специальное свойство и он будет выглядеть как Риббон.

Недостатки А теперь поговорим о грустном. Недостатков полно, не только из-за кучи багов (все конечно будет исправлено в финальной версии), а в самой концепции, придуманной Майкрософтом.

Фиксированная высота Этот фактор является наиболее ограничивающим, так как делает невозможным применение ленты в диалоговых окнах (как никак, 150 пикселей), хотя исключения, безусловно, найдутся.

Внешний вид Под пестроту ленты придется подстраивать весь внешний вид программы. Ибо обычные кнопки и панели точно не будут сочетаться. Если не хотите, чтобы на вас показывали пальцем – постарайтесь запастись соответствующими компонентами.


Низкая производительность Если использовать слишком много контроллов, приложение начинает заметно тормозить (при перерисовке). За примером далеко ходить не надо – возьмите ту же демонстрационную программу.

Отсутствие свойств выравнивания групп (align). Это очень большая недоработка. Иногда хочется растянуть группу по всей ленте, но нельзя. Будем надеяться, что в финальной версии данная возможность будет реализована.

Заключение Подводя итог, хочется отметить, что рибон (по крайне мере в текущей реализации) не придет на смену привычным меню. Жестко заданный размер не позволит разработчику использовать ленту в диалоговых окнах, вся эта красота заставит подгонять все остальные элементы управления под один стиль. Риббон займет свое место, но полностью вытеснить обычные тулбары ему вряд ли удасться (да и, скорее всего, разработчики не ставят перед собой такую задачу). Напоследок, посоветую использовать ленту только там, где она действительно нужна. Не стоит внедрять ее в свое приложение только из-за красоты или следования моде. Примечание: статья писалась больше полугода назад, однако актуальности она не потеряла. Появились достойные реализации Ribbon'а от других разработчиков, да и TMS на пару с DevExpress обновили свои продукты, избавив их от некоторых ошибок.

By: Коновалов Сергей 2007 E-mail: Sergio_Lightning@yahoo.com


Введение в теорию конечных автоматов Нет, это не лекция по дискретной математике! Это просто статья, цель которой - рассказать читателю, что такое конечные автоматы и где они применяются в повседневной программистской деятельности. Сейчас мы рассмотрим самую простую, наглядную и понятную абсолютно всем задачу – проверку на то, является ли строка, введенная пользователем, действительным числом. Конечно, для такой цели на практике чаще всего используются функция TryStrToFloat, процедура Val или блок обработки исключений try...except..end. Поэтому замечу, что данный пример является учебным, но, поняв его, мы сможем перейти к решению более полезных и сложных задач. Одной из таких задач является анализ текстового файла, описывающего таблицу записей. Каждая строка в нем является отдельной записью и имеет поля, разделяемые друг от друга запятыми. Причем поле может быть записано в кавычках (вследствие чего в значении поля также могут оказаться запятые), а может быть пустым. Но это тема следующей статьи. Теперь перейдем к решению поставленной перед нами задачи. Чтобы ее решить, необходимо будет построить блок-схему конечного автомата и реализовать ее на языке программирования (в данном случае на Delphi). Поскольку по определению конечный автомат – это система, которая переходит из одного состояния в другое в соответствии с входными данными, - нам нужно составить список состояний и определить правила переходов между ними. Делать это мы будем графически, рисуя состояния кружочками, внутри которых будем писать идентификатор данного состояния, обозначая переходы между ними стрелками, а правила перехода – надписями над этими стрелками. Стрелка, не имеющая начала означает, что текущее состояние не изменяется. DecSep – это разделитель дробной и целой части, обычно точка или запятая. Сейчас приведу конечный результат, а далее поясню, как он получился.

“+” “-“

B

A

“0”..“9“

“0”..“9“

“0”..“9“ C

“0”..“9“

DecSep D

“0”..“9“

E “E”..“e“

“E”..“e“ F “+” “-“

G

“0”..“9“ “0”..“9“

H

“0”..“9“


• • • • • • • •

начало считывания строки, получение первого символа; получен знак; получена первая цифра числа; получен разделитель; получена первая цифра после разделителя; получена спецификация E; получен знак после спецификации; получена первая цифра после спецификации.

Рисуем состояние A. Это начальное состояние, то есть состояние, в котором ожидается прием первого символа. Первый символ может быть либо знаком, либо цифрой. В связи с этим вводим два новых состояния: B(после получения знака) и C(после получения первой цифры). Продолжая эту процедуру, в самом конце получим именно то, что изображено на картинке. Теперь необходимо определить, когда возникает ошибка. Она может возникнуть в двух случаях: если в текущем состоянии принят символ, который не может быть им обработан, либо если вся строка считана, а текущим состоянием не является одно из следующих: C, E, H. Кажется, что запрограммировать такой алгоритм чрезвычайно сложно, но это не так. Ниже приведен исходный код функции с подробными комментариями. // все состояния храним в перечислимом типе // для облегчения понимания даны осмысленные имена TState = (stStartState, stGotSign, stGotInitDigit, stGotDecPt, stScanDigits, stGotE, stGotESign, stGotEDigit); {функция, проверяющая, является ли строка числом, записанным в } {экспоненциальной форме: если да, то возвращает -1, нет – номер} {позиции, в которой находится недопустимый символ } function CheckNumber(const S: string): Integer; var I: Integer; // переменная цикла Ch: Char; // полученный символ CurState: TState; // текущее состояние begin if S = '' then begin Result := 1; // строка пуста Exit; end; CurState := stStartState; { начало сканирования } { просмотр строки выполняется всего 1 раз! } for I := 1 to Length(S) do begin Ch := S[I]; case CurState of { начало сканирования } stStartState: begin if Ch in ['+', '-'] then CurState := stGotSign else if Ch in ['0'..'9'] then


CurState := stGotInitDigit else begin Result := I; // ошибка в I-ом символе Exit; end; end; { был получен знак } stGotSign: begin if Ch in ['0'..'9'] then CurState := stGotInitDigit else begin Result := I; Exit; end; end; { была получена первая цифра } stGotInitDigit: begin if Ch = DecimalSeparator then CurState := stGotDecPt else if Ch in ['E', 'e'] then CurState := stGotE else if not (Ch in ['0'..'9']) then begin Result := I; Exit; end; end; { был получен разделитель } stGotDecPt: begin if Ch in ['0'..'9'] then CurState := stScanDigits else begin Result := I; Exit; end; end; { была получена первая цифра после разделителя } stScanDigits: begin if Ch in ['E', 'e'] then CurState := stGotE else if not (Ch in ['0'..'9']) then begin Result := I; Exit; end; end; { была получена спецификация Е } stGotE: begin


if Ch in ['+', '-'] then CurState := stGotESign else if Ch in ['0'..'9'] then CurState := stGotEDigit else begin Result := I; Exit; end; end; { был получен знак после Е } stGotESign: begin if Ch in ['0'..'9'] then CurState := stGotEDigit else begin Result := I; Exit; end; end; { была получена первая цифра после Е } stGotEDigit: begin if not (Ch in ['0'..'9']) then begin Result := I; Exit; end; end; end; // case end; // for // проверка конечного соcтояния if (CurState = stGotInitDigit) or (CurState = stScanDigits) or (CurState = stGotEDigit) then Result := -1 // ошибок нет else { ошибка: число набрано не до конца } Result := Length(S) + 1; end; Как видите, ничего сложного. Все довольно просто и понятно J.

By: Darvin E-mail: darvin89@inbox.ru


Создание изображений с использованием OpenGL. Работа с графикой – одно из самых любимых моих занятий, поэтому, когда мне предложили выполнить лабораторную работу и описать, я согласился, несмотря на то, что с OpenGL не работал более 3 лет. Сегодня мы рассмотрим одну из лаб, которую дают на третьем курсе факультета прикладной математики в МАИ, спецкурс «Компьютерная графика». Я немного усложнил задание, так что читай, будет интересно.

Задание Тема: Создание изображений с использованием библиотеки OpenGL. Задание: Аппроксимировать заданную поверхность полигональной сеткой и средствами OpenGL обеспечить для нее • • • • •

возможность изображения в параллельной и перспективной проекции возможность удаления невидимых линий и поверхностей возможность реалистичного освещения возможность каркасного изображения возможность пространственных аффинных преобразований

Исходные данные, определяющие поверхность, должны считываться из текстового файла. Формат представления исходных данных разрабатывается студентам самостоятельно. В зависимости от номера студента, предоставляются на выбор следующие поверхности: Сфера с вырезами, Конус с вырезами, Цилиндр с вырезами и т. д.

Усложняем Что такое аппроксимировать поверхность? Если посмотреть на фигуры, которые нам предлагают, то видно, что все они имеют форму с изгибами. Невозможно создать в компьютерной графики сферу, можно только рисовать точки и линии, а окружности создаются с помощью большого количества линий (трехмерные объекты из треугольников). Чем больше линий, тем более округлой будет получаться форма объекта. Аппроксимировать – означает создать объект максимально приближенный к реальному. А на сколько приближенным нужно делать в данном задании? Ладно, выберем степень соответствия на свое усмотрение.

Создание сферы с помощью линий. Слева сфера из малого количества линий, поэтому она угловатая. Справа линий в четыре раза больше, поэтому этот объект больше похож на сферу.


Данные необходимо загружать из файла, но это же серьезное упущение. Отображение должно происходить полигональным методом, поэтому какая разница, какие данные в файле – сфера, цилиндр или пышные формы Памеллы Андерсон? Достаточно одному студенту выполнить задание, а все остальные должны только чуть изменить формат файла и переменные в исходнике чтобы идентичность кода не бросалась в глаза. После этого нужно, создать необходимую фигуру в 3DS Max, сохранить ее в файле и можно считать, что задание выполнено. Мы усложним задачу и будем генерить фигуру программно. Уж на третьем курсе факультета математики должны уже уметь математически создать сферу или цилиндр.

Инициализация Итак, давай напишем программку, которая будет динамически формировать сферу. Для начала создадим пустое Win32 приложение и сразу же добавим необходимые заголовочные файлы, а именно: #include <gl\gl.h> #include <math.h> Первый заголовочный файл подключает функции OpenGL, которые нам предстоит использовать для отображения сферы. Вторая строка подключает математические функции. Так как сфера будет генериться, нам понадобятся тригонометрические функции из math.h. Теперь идем в свойства проекта и в свойствах линкера в строке Additional Dependencies добавляем библиотеку opengl32.lib. Библиотеку расширенных функций glu использовать не будем. Да, она упростила бы создание сферы, цилиндра и т.д., но судя по заданию, мы не имеем права обращаться к ней. Среди глобальных переменных нам понадобиться переменная hrc типа HGLRC, в которой будем сохранять контекст рисования OpenGL. Теперь движемся в функцию InitInstance, где создается окно. После его создания добавляем код из листинга 1. Давай рассмотрим этот код, потому что его понимание необходимо для выполнения задания. После получения контекста рисования окна, мы должны задать формат пикселя. В данном случае я использую RGBA формат в 24 бита. Помимо этого, для повышения скорости включается двойная буферизация (включен флаг PFD_DOUBLEBUFFER). В исходнике, который ты найдешь на компакт диске, задание формата пикселя я вынес в отдельную функцию SetPixelFormat. Далее идет создание контекста рисования OpenGL с помощью фукнкции wglCreateContext и делаем его текущим wglMakeCurrent. Это стандартная операция при инициализации OpenGL. Теперь идет самое интересное. В задании есть необходимость удаления невидимых линий и поверхностей. Но как это сделать? Для OpenGL ничего сложного тут нет, нужно только включить тест глубины. Без него выводиться все подряд и объекты, которые находяться дальше, могут оказаться в одной позиции с более близкими объектами. Чтобы включить тест глубины пишем: glEnable(GL_DEPTH_TEST);


Файл поможет узнать, какие есть тесты глубины и чем они отличаются

Существует множество вариантов тестов глубины и их можно задать с момощью функции glDepthFunc. В данном примере я использую тест GL_LESS. Этот тест используется по умолчанию, а какие еще существуют тесты можешь узнать из файла справки по функции glDepthFunc.

Освещение Освещение – это отдельная песня. В задании сказано, что мы должны обеспечить реалистичное освещение. Но как это сделать? Реалистичное освещение – это целая наука. Существует множество алгоритмов и методов и OpenGL может практически все. Самое реалистичное, на мой взгляд, освещение можно получить только с использованием вершинных шейдеров, но я надеюсь, что составители задания не пошли так далеко и не испортили нам жизнь, поэтому в данном примере я использую освещение, предоставляемое функциями OpenGL. Итак, чтобы в нашей сцене появился источник освещения, необходимо создать источник освещения, выбрать модель и указать его положение. В OpenGL первый пункт достаточно прост, потому что нам уже доступны источники с именами GL_LIGHTi, где i изменяется от 0 до GL_MAX_LIGHTS. В моем заголовочном файле эта константа равна 0x0D31. Я думаю, этого вполне достаточно. Мы будем использовать один источник освещения GL_LIGHT0. Чтобы задать его положение, используем следующий код:


GLfloat position[] = { 0.0, 1, -1.5, 0.0 }; glLightfv(GL_LIGHT0, GL_POSITION, position); В первой строке задаем массив из четырех чисел типа GLfloat, которые определят позицию источника освещения в нашем мире. Во второй строке вызываем функцию glLightfv. У нее три параметра: 1. Источник освещения; 2. Параметр, который нужно изменить. Мы будем устанавливать позицию света, поэтому указываем константу GL_POSITION; 3. Вектор (массив из четырех значений), задающий позицию. Эта позиция будет трансформирована в матрицу modelview нашего мира. Если не задать положение источника света, то по умолчанию будет использоваться вектор (0,0,1,0). Можно еще задать тип лампочки – рассеянный свет, прожектор и т.д., но это уже дело вкуса и цвета, а в задании нам не сказали, какого именно нужен свет. Позицию лампочки задали, теперь необходимо включить свет. Нет, для этого не нужно вызывать электрика, нужно просто написать две следующие строки: glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); В первой строке мы разрешаем освещение вообще, а во второй включаем источник освещения GL_LIGHT0. Как мы уже знаем, в OpenGL существует тонна и еще маленький вагончик предопределенных лампочек, но чтобы они работали, нужно включить те, которые необходимы.

Постинсталл Чтобы освещение нормально работало, желательно включить нормализацию: glEnable(GL_NORMALIZE); glEnable(GL_AUTO_NORMAL); Несмотря на то, что при построении сферы я буду рассчитывать нормаль ручками, эти два флага включены, чтобы ты знал о их существовании. Первый флаг разрешает нормализацию, а второй разрешает это делать автоматом. В задании есть необходимость предоставить возможность отображения фигуры в виде каркаса. Как это сделать? Очень просто. Наш полигон будет строиться из закрашенных треугольников. Чтобы убрать закраску, можно добавить следующую строку: glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); Здесь мы включаем отображение передних и задних поверхностей полигона линиями. Теперь это не закрашенные треугольники, а линии, а значит, OpenGL нарисует только каркас.

Проекция В задании есть пункт, который требует от нас обеспечить возможность отображения в параллельной и перспективной проекции. Для начала, определимся, что такое параллельная и перспективная проекция. При параллельной проекции все объекты проецируются на плоскость


просмотра параллельно, без каких-либо искажений. Но наши глаза не обладают такой широтой обзора, поэтому человеку присуще перспективное зрение и оно кажется более естественным.

Параллельная проекция

Для работы с параллельной проекцией используется функция glOrtho: void glOrtho(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near, GLdouble far) Уже по названиям переменных можно понять, что первые четыре задают прямоугольник просмотра, а последние две расстояние до ближней плоскости отсечения и дальней соответственно. Все, что ближе near и дальше far не будет отображаться.

Перспективная проекция

Чтобы задать перспективную проекцию, нужно использовать функцию gluPerspective: void gluPerspective(GLdouble angley, GLdouble aspect, GLdouble znear, GLdouble zfar) Первый параметр – это угол обзора по оси Y. Второй параметр – соотношение сторон, в большинстве случаев этот параметр делают равным отношению ширины окна к высоте. Далее идут расстояния до ближней плоскости отсечения и дальней.


Вполне логичным будет задавать параметры проекции по событию изменения размеров окна, ведь от этого зависят и параметры отображения. Идем в функцию WndProc и добавляем туда обработчик события WM_SIZE, код которого можно увидеть в листинге 2. В данном примере будем использовать параллельное проецирование. Чтобы превратить его в перспективу, замени вызов glOrtho на следующую строку: gluPerspective(45.0f, width/height, 1.0f, 100.0f); Данная перспектива будет иметь следующие характеристики: 1. Угол обзора в высоту равен 45 градусам. 2. Соотношение сторон просмотра соответствует соотношению сторон окна. 3. Все вершины/объекты ближе 1 и больше 100 не будут видны. Перед установкой необходимой проекции необходимо выбрать режим матрицы GL_PROJECTION, а после этого загрузить единичную матрицу с помощью функции glLoadIdentity().

Отображение Для отображения сцены, по событию WM_PAINT добавляем вызов функции DrawScene. Эту функцию я приводить здесь не буду, потому что в ней происходит генерация сферы математическим методом и получился достаточно не маленький код. Если бы я загружал сферу из файла, то получилось бы не намного меньше, но адаптировать пример не составляет труда, если ты умеешь работать с файлами.

Результат работы примера, отображает сферу в виде каркаса

Когда будешь изучать исходник, обрати внимания, что для каждого треугольника я определяю нормали, без которых освещение станет невозможным. Просто сформировать полигон мало, необходимо нормализовать его, чтобы OpenGL знал, как должен вести себя источник освещения.


Помимо этого, объекту назначается материал. В зависимости от материала поверхности, изменяется и освещение. Глянцевые поверхности должны отбрасывать блики, а матовые просто равномерно рассеивают свет. Размер блика также может отличаться в зависимости от поверхности. Освещение – это целая наука, и ее невозможно изучить в одной статье, но надеюсь, что предоставленный нами пример поможет тебе.

Результат сферы с закрашенными полигонами

Полный код примера, ты найдешь на компакт диске. Чего не хватает в примере? Пространственных преобразований и выреза. Вырез сделать программно не так уж и сложно, а вот преобразования – достаточно интересная тема и для нее нужна отдельная статья. Для DirectX преобразования я описывал в своих книгах "Искусство программирования игр на C++" и "Direct и С++ искусство программирования". Вторая книга имеет вариант и для Delphi.

Зачет Исходный код готового примера – это конечно хорошо, но нужно уметь еще объяснить этот код, а лучше все же знать предмет. Существет множество вариантов тестов глубины, алгоритмов освещений и если ты не знаешь темы, то знающий препод сможет легко поставить тебя в тупик. Так что если ты прогулял целый семестр и не знаешь OpenGL, купи книгу и наверстай упущенное. Это необходимо не только для сдачи экзаменов, но и просто для себя, ведь мы учимся не ради оценок, а ради знаний, без которых после института диплом ни стоит ничего. Несмотря на то, что мы описали решения одной из лаб, мы не выполняем лабораторные за деньги и тем более бесплатно, поэтому, с подобными вопросами просьба в мыло не стучаться. Я могу выполнить работу за тебя, но у меня есть своя работа, жена и дети, а твои знания стоят дороже. На последок хочу перефразировать одну замечательную мудрость на современный лад: "ученье – деньги, слава и достойная жизнь, а не учение – бедность, алкоголизм, а с нынешними ценами на жилье, можно стать бомжом".


Листинг 1 // Определение контеста рисования окна

HDC dc = GetDC(hWnd); // Задаем формат пикселя. static PIXELFORMATDESCRIPTOR pfd = { sizeof(PIXELFORMATDESCRIPTOR), 1, PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, PFD_TYPE_RGBA, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, PFD_MAIN_PLANE, 0, 0, 0, 0 }; // Выбираем созданный формат int pixelFormat = ::ChoosePixelFormat(dc, &pfd); if (pixelFormat == 0){ MessageBox(0, "ChoosePixelFormat error", "Error", MB_OK); return FALSE; } // Установить формат пикселя if (::SetPixelFormat(dc, pixelFormat, &pfd) == FALSE) { MessageBox(0, "SetPixelFormat error", "Error", MB_OK); return FALSE; } // Создаем контекст рисования OpenGL hrc = wglCreateContext(dc); wglMakeCurrent(dc,hrc); // Позиция источника освещения и позиция просмотра GLfloat position[] = { 0.0, 1, -1.5, 0.0 }; GLfloat local_view[] = { 0.0 }; // Включаем тест глубины glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LESS); // Включаем освещение glLightfv(GL_LIGHT0, GL_POSITION, position); glLightModelfv(GL_LIGHT_MODEL_LOCAL_VIEWER, local_view); glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); // включаем номализацию glEnable(GL_AUTO_NORMAL); glEnable(GL_NORMALIZE);


// Раскоментировать следующую строку, чтобы отображать в карксном виде //glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); // задать размеры окна MoveWindow(hWnd, 10, 10, 640, 480, TRUE); Листинг 2 case WM_SIZE: // Определяем размеры окна RECT r; GetWindowRect(hWnd, &r); width = r.right-r.left; height = r.bottom-r.top; // Использовать матрицу Projection glMatrixMode(GL_PROJECTION); // Загрузить единичную матрицу преобразований glLoadIdentity(); // В зависимости от размеров окна создаем параллельную проекцию if (width <= height) glOrtho (-2, 2, -2*height/width, 2*height/width, 2.0, -2.0); else glOrtho (-2*width/height, 2*width/height, -2, 2, 2.0, -2.0); // задаем область просмотра glViewport(0, 0, width, height); break; Листинг 3 void DrawScene(HWND hWnd) { PAINTSTRUCT ps; HDC hdc; hdc = BeginPaint(hWnd, &ps); // сохранить матрицу преобразований glPushMatrix(); // Настроить тест глубины glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LESS); glDepthFunc(GL_LEQUAL); // очистка буферов glClearColor(1,0.5,0.5,1); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // вывод полигона glBegin(GL_TRIANGLES);


// Здесь необходимо вывести координаты треугольников // из которых будет строиться сфера glEnd(); // восстановить матрицу преобразований glPopMatrix(); // завершить формирование буфера и переключить задний буфер glFlush(); SwapBuffers(hdc); EndPaint(hWnd, &ps); }

Исходники примера ищи в архиве opengl.zip By: Фленов Михаил aka Horrific E-mail: horrific@vr-online.ru WWW: www.vr-online.ru


Счетчик трафика на Delphi Во времена сумасшедшего использования таких технологий, как GPRS/EDGE, DSL, приходится всерьез задумываться о потраченном трафике. Цены на него, конечно, падают, но и потребности наши возрастают. Если пытаться запомнить, сколько ты скачал вчера, а сколько сегодня, то можно начинать собираться в психушку, поскольку от таких расчетов мозг, скорее всего, сильно заглючит. Чтобы этого не случилось, мы покажем тебе, как можно автоматизировать процесс подсчета трафика.

Обзор инструментария Поскольку наша программа будет иметь графический интерфейс, то и писать ее лучше всего на Delphi. Версия любимой среды разработки значения не имеет. Помимо Delphi, нам потребуется доступ в инет для заглядывания в MSDN, а если ты не в ладах с английским языком, то не забудь обзавестись англо-русским словарем, поскольку всю информацию Microsoft приводит на языке посетителей порносайтов.

Теоретическая часть Перед рассмотрением практического примера, необходимо ознакомиться с теорией, поэтому отложи клаву подальше и приготовься к впитыванию инфы. Получить информацию о трафике можно несколькими способами. Мы воспользуемся самым продвинутым - функцией GetIfTable из библиотеки IPHLPAPI.DLL (в модулях, идущих вместе с Delphi, ее описания нет). Эта функция позволяет с легкостью получить информацию о трафике и сетевых интерфейсах. Работать с ней очень просто, а библиотека, помимо нее, содержит еще массу функций для получения всевозможной информации о работе сети и т.д. Эта библиотека существует и в Windows 9x, и в ее последних версиях (2K/XP/2K3/Vista), где она отличается появлением новых функций (изменение структур). Чтобы не волноваться за работоспособность своей программы, советую обратиться к MSDN и сделать в коде проверку версии Windows. В своем примере я буду ориентироваться на все еще самую популярную в народе Windows XP. Итак, как я уже сказал, модуля, в котором были бы описаны структуры и функции этой библиотеки, вместе с Delphi не идет, поэтому нам в своем проекте придется их описывать самостоятельно. Первым делом давай рассмотрим функцию, которая нам понадобится. function GetIfTable( pIfTable: PMIB_IFTABLE; pdwSize: PULONG; bOrder: BOOL; ):DWORD; pIfTable - указатель на структуру MIB_IFTABLE pwdSize - буфер для получения данных таблицы MIB_IFTABLE bOrder – сортировка При успешном выполнении функция возвращает NO_ERROR, в противном случае - код ошибки. После выполнения функции все данные запишутся в структуру pIfTable, указатель которой передается в первом параметре. Структура MIB_IF_TABLE выглядит следующим образом:


TMibIfTable = packed record dwNumEntries : DWORD; Table : TMibIfArray; end; dwNumEntries - количество сетевых интерфейсов table - массив структур MIB_IF_ROW После успешного выполнения в dwNumEntries будет содержаться количество сетевых интерфейсов. Пробежавшись по ним в цикле, мы сможем получить всю необходимую нам информацию. Информация о каждом интерфейсе будет храниться в соответствующей MIBтаблице. Структуру MIB таблицы нам также придется объявлять самим. TMibIfRow = packed record wszName : array[0..255] of WideChar; dwIndex : DWORD; dwType : DWORD; dwMtu : DWORD; dwSpeed : DWORD; dwPhysAddrLen : DWORD; bPhysAddr : array[0..7] of Byte; dwAdminStatus : DWORD; dwOperStatus : DWORD; dwLastChange : DWORD; dwInOctets : DWORD; dwInUcastPkts : DWORD; dwInNUCastPkts : DWORD; dwInDiscards : DWORD; dwInErrors : DWORD; dwInUnknownProtos : DWORD; dwOutOctets : DWORD; dwOutUCastPkts : DWORD; dwOutNUCastPkts : DWORD; dwOutDiscards : DWORD; dwOutErrors : DWORD; dwOutQLen : DWORD; dwDescrLen : DWORD; bDescr : array[0..255] of Char; end; Не пугайся такого большого количества свойств :), сейчас я поясню, что они собой представляют: wszName - имя сетевого интерфейса. В последних версиях Windows это свойство заменяет Alias. dwIndex - порядковый номер соответствующего интерфейса. dwType - тип интерфейса. Может быть: • • • •

IF_TYPE_OTHER (1) - неизвестный сетевой интерфейс; IF_TYPE_ETHERNET_CSMACD (6) – Ethernet; IF_TYPE_ISO88025_TOKENRING (9) - Token ring; IF_TYPE_PPP (23) – PPP;


• • • • •

IF_TYPE_SOFTWARE_LOOPBACK (24) – Lookback; IF_TYPE_ATM (37) – ATM; IF_TYPE_IEEE80211 - IEEE 802.11; IF_TYPE_TUNNEL (131) – tunnel; IF_TYPE_IEEE1394 (144) - IEEE 1394.

dwMTU - максимальная скорость передачи. dwSpeed - скорость передачи данных (биты/сек). dwPhysAddrLen - длина физического адреса устройства. bPhysAddr - физический адрес интерфейса. dwAdminStatus - активность интерфейса. Описание принимаемых значений смотри в MSDN. dwOperStatus - текущий статус интерфейса. Опять же может принимать множество значений, поэтому чтобы не тратить место в статье, снова направляю тебя к MSDN. dwLastChange - последний измененный статус. dwInOctets - количество байт, принятых через определенный интерфейс. dwInUcastPkts - количество направленных пакетов, принятых интерфейсом. dwInNUCastPkts - количество ненаправленных пакетов, принятых интерфейсом. dwInDiscards - количество входящих забракованных пакетов. dwInErrors - количество принятых пакетов, содержащих ошибки. dwInUnknownProtos - количество принятых забракованных пакетов с неизвестным протоколом. dwOutOctets - количество байт, отправленных через определенный интерфейс. dwOutUCastPkts - противоположно dwInUcastPkts. dwOutNUCastPkts - противоположно dwInNUCastPkts. dwOutDiscards - противоположно dwInDiscards. dwOutErrors - противоположно dwInErrors. dwOutQLen - длина очереди данных. dwDescrLen - размер bDescr. bDescr - описание интерфейса. Более полное описание этой структуры ты найдешь в MSDN.

Пример использования Теперь, когда мы владеем всей необходимой информацией, самое время написать примерчик. Итак, запускай Delphi, создавай новый проект типа Application и кидай на форму таймер и ListView. Создай в нем 8 столбцов: 1. Интерфейс 2. Тип интерфейса 3. Физический адрес 4. Скорость 5. Отправлено 6. Принято 7. ErrorOut 8. ErrorIn В ListView мы будем отображать всю полученную информацию, поэтому нужно придать ему соответствующий вид. Выстави следующие свойства: GridLines = true HotTrack = true RowSelect = true ViewStyle = vsReport


Форма готова, можно переходить к кодингу. Придвинь к себе клавиатуру и первым делом опиши в модуле проекта все структуры, которые мы разбирали: MibIfRow, MibIfTable. Помимо этого, объяви новый тип TMacAddress=array[1..8] и TMibIfArray=array[0..512] of TMibIfRow. В TMacAddress мы будем хранить физический адрес устройства. Для всех созданных структур создай указатель. Когда опишешь все структуры, не забудь объявить нашу функцию. Делается это следующим образом. После раздела var модуля нашей формы напиши: function GetIfTable(pIfTable:PMibIfTable; pdwSize: PULONG; bOrder: boolean ): DWORD; stdCall; external 'IPHLPAPI.DLL'; Если ты работал с библиотеками DLL, то тебе должно быть все понятно, в противном случае знай, что таким образом можно обращаться к функциям, которые находятся в Dll. Создай обработчик события OnTimer для нашего таймера и напиши в нем код из соответствующей врезки, а я объясню, что в нем происходит. Перед использованием GetIfTable нужно выделить необходимую память под структуру MibIfTable. Память выделяется с помощью New. Все, память выделили, а значит, можно попытаться вызвать функцию GetIfTable. Результат ее выполнения запишется в переменную _error. Теперь проверь значение этой переменной. Если оно не равно NO_ERROR, то это означает, что тебя посетила птица обломинго и нужно показать печальное сообщение, прервать выполнение процедуры и сверить свой код с листингом. Если же все нормально, то в цикле можно начинать разбирать наши данные. Как я уже говорил, в dwNumEntries структуры TMibIfTable хранится количество интерфейсов. Запускаем цикл от 0 до dwNumEntries-1 и начинаем радоваться полученной информации. При добавлении в ListView я использую функции для преобразования полученных данных. Зачем? Отвечаю. Например, значение dwOutOctets приводится в байтах. Не думаю, что в программе учета трафика будет удобно смотреть на большое количество постоянно меняющихся цифр. Поэтому можно реализовать отображение трафика в привычных нам единицах: Кб, Мб, Гб. Чтобы решить эту задачу, я создал функцию, которая будет высчитывать трафик в определенных единицах измерения информации. Код приводить не буду - если ты немного знаком с Delphi, то проблем с написанием подобного кода у тебя не возникнет. Подобную же функцию я создал для определения скорости соединения. В моем проекте она называется SpeedToStr(). Ее код идентичен функции Traff, изменены только константы, в которых хранятся значения каждой единицы измерения скорости (bps, Kbps, Mbps). В самом начале статьи я рассказывал тебе про несколько типов возможных сетевых интерфейсов. После выполнения функции мы получаем числовой идентификатор типа, переварить который без заглядывания в руководства сможет разве что Крис Касперски. Чтобы не обидеть абсолютное большинство пользователей, мы используем функцию: GetInterfaceType(types:integer):string; В качестве параметра ей нужно передать идентификатор типа интерфейса, а она, в свою очередь, вернет нам его символьное имя. Код функции ты можешь увидеть в исходнике, который дожидается тебя на нашем диске. Вернемся к основному коду программы. Обрати внимание, как я получаю физический адрес интерфейса. Поскольку адрес хранится в массиве типа byte, нам нужно написать функцию, которая бы приводила его в понятный человеку вид. Чтобы это сделать, я создал еще одну функцию:


GetStrMac(Mac: TMacAddress; size: integer): string;. В качестве параметров ей нужно передать тип TMacAddress (о нем я говорил в самом начале статьи, и ты должен был уже описать его в своем проекте) и длину физического адреса. Все данные берутся из заполненной структуры. Для экономии журнального места я не буду приводить здесь весь код программы - его ты сможешь найти на DVD. Здесь я лишь вкратце расскажу тебе, что в нем происходит. Первым делом в коде функции я делаю проверку параметра size. Если он равен нулю, то физический адрес просто отсутствует. Чтобы как-то представить это пользователю, в качестве результата я просто возвращаю «00» в привычном для отображения MAC-адреса виде. Если же size не равен нулю, в цикле необходимо получать данные из массива, в котором хранится адрес, и с помощью функции IntToHex приводить его к шестнадцатеричному виду и разделять символом «-». После того как разбор завершится, нужно удалить самый последний символ нашего результата, которым всегда будет лишнее «-». Чтобы все было красиво, я его удаляю и возвращаю результат.

Он сказал: «Конец» Наш пример готов для компиляции и жестокого тестирования. Начало созданию своей программы для учета трафика положено, тебе остается только усовершенствовать пример. После этого ты сможешь следить за тем, сколько драгоценного трафика ты тратишь, или даже и сделать на основе нашего исходника свою платную программу и продавать ее злым буржуям за 19,99 в месяц. Листинг var _MibIfTable:PMibIfTable; _P:Pointer; i:integer; _buflen:dword; _error:dword; begin listview1.Items.Clear; _buflen:=sizeof(_MibIfTable^); New(_MibIfTable); _P:=_MibIfTable; _error:=GetIfTable(_MibIfTable, @_buflen, false); if _error <> NO_ERROR then begin ShowMessage('Произошла ошибка!'); Exit; end; for i:=0 to TMibIfTable(_P^).dwNumEntries-1 do with ListView1.Items.Add do begin caption:=Trim(TMibIfTable(_p^).table[i].bDescr); subitems.Add(GetInterfaceType(TMibIfTable(_P^).table[i].dwtype));


subitems.Add(GetStrMac(TMacAddress(TMibIfTable(_p^).Table[i].bP hysAddr), TMibIfTable(_p^).table[i].dwPhysAddrLen)); subitems.add(SpeedToStr(TMibIfTable(_p^).table[i].dwSpeed)); subitems.Add(Traff(TMibIfTable(_p^).table[i].dwOutOctets)); subitems.Add(Traff(TMibIfTable(_p^).table[i].dwInOctets)); subitems.Add(IntToStr(TMibIfTable(_p^).table[i].dwOutErrors)); subitems.Add(IntToStr(TMibIfTable(_p^).table[i].dwInErrors)); end; dispose(_MibIfTable); Исходники примера ты сможешь получить, отправив вежливое письмо на мыльник автора J.

By, Антонов Игорь aka Spider_NET E-mail: antonov.igor.khv@gmail.com WWW: www.vr-online.ru


Ядра – чистый изумруд Даже если ты не собираешься писать драйвера, эта статья пригодиться тебе для понимания некоторых внутренних особенностей ОС Windows. Несмотря на то, что Microsoft заботиться о программерах и предоставляет достаточно подробную информацию об API, архитектура ОС остается наименее открытой и информация обрывочна. Мы постарались собрать в этой небольшой статье самое интересное о ядре Windows и рассказать тебе.

Набор инструментов Для создания драйвера под Windows необходим специальный набор инструментов, который называется DDK (Driver Development Kit). Помимо этого, желательно установить специализированную версию Windows, которая содержит отладочную информацию. Это поможет в отладке драйвера, что не такое уж и простое занятие. Набор DDK существует для каждой версии ОС свой и распространяется отдельно от компилятора. Это значит, что если даже у тебя установлена полная версия Visual Studio, DDK придется ставить отдельно. Причем, просто так скачать его с сайта Microsoft нельзя, потому что он распространяется вместе с платной подпиской MSDN.

Официальный DDK на сайте Microsoft


Если

у

тебя

есть

лишняя

денежка,

то

DDK

можно

заказать

здесь:

http://www.microsoft.com/whdc/devtools/ddk/default.mspx. На самом деле DDK дают на халяву, деньги

нужны за пересылку компакт диска. Существуют и не официальные версии и залежи наборов для разработчиков драйверов, но для их поиска используй ya.ru.

Язык программирования Стандартом при написании драйверов является язык С. Да, именно он. Конечно, можно ухитриться написать на С++, но я бы не рекомендовал играть с объектами. Можно написать драйвер даже на Delphi, но только старой версии (если не ошибаюсь, до Delphi 5) и для сборки проекта в драйвер все равно понадобиться DDK, потому что стандартные компиляторы не умеют собирать необходимый бинарник.

Властелин колец Как только процесоры от Intel стали 32-х разрядными (а это произошло начиная с процессора 386), их возможности значительно расширились. Процессор научился работать в четырех режимах, нумируемых от 0 до 4. Эти режимы называют кольцами от RING0 до RING3. В окнах используется только два кольца и два конца, в смысле поддерживаются только RING0 и RING3. Уровень RING0 самый привелегерованный. На нем доступны абсолютно все возможности процессора, прямой доступ к железу, памяти и много всего вкусного, но здесь может работать только ядро и драйвера ОС. Погльзовательские процессы, работают на 3-м уровне с большими ограничениями. Именно поэтому хакеры так мечтают очутиться на нулевом кольце. Дабы изолировать драйвера от пользовательских программ, окна выделили для них разные участки памяти. Таким образом, пользовательский процесс никогда не сможет вмешаться в работу драйвера или ядра.

Наиболее простая схема архитектуры Windows


При описании архитектуры в разных источниках даются разные схемы. Скорей всего это связано с тем, что эта часть ОС не так открыта, как Windows API и при описании некоторых компонентов мы можем только догадываться о их существовании именно в данном месте и именно с такими связями. Но достоверно ясно, что режим ядра изолирован от пользовательских процессов, но мы можем обращаться к ним через библиотеку NTDLL.DLL или вызывая функции напрямую. Прямой вызов не является хорошим решением, но и нельзя его отнести к запрещенным способам. Отрицательный момент кроется в том, что MS может поменять функцию в ядре или предоставить новую версию и при работе через NTDLL перекомпиляция программы не понадобиться. В реальности такие случаи происходят очень редко, и старые функции не исчезают, а продолжают поддерживаться для обеспечения обратной совместимости.

Режим драйвера Драйвера можно разделить на две большие группы – пользовательские (User-mode driver) и ядерные (Kernel mode driver). Есть ещё куча различных классификаций, но в них нет смысла. В принципе, какая разница, в какую группу засунуть драйвер различные специалисты, главное, чтобы он выполнял поставленную перед ним задачу. Драйвер может быть монолитным и многоуровневым. В первом случае все функции берет на себя один единственный драйвер. Он намного проще в отладке, но не всегда эффективен. Намного лучше (и поэтому чаще можно встретить) многоуровневые драйверы. Их можно сравнить с классами в С++. Каждый уровень выполняет небольшую задачу, но делает это хорошо. Выполнив необходимые действия, управление передается драйверу следующего уровня, где обработка продолжается. Отлаживать многоуровневые драйвера немного сложнее, но зато один раз отшлифованный уровень будет работать великолепно, потому что он выполняет небольшую задачу. Многие бояться множественных уровней, и четко не понимают преимуществ. Рассмотрим на примере сетевой карты. Для получения данных из сети можно создать два драйвера. Первый будет разбирать пользовательский запрос, а второй читать данные из сетевой карты. Чтобы написать снифер или сетевой экран, нужно написать драйвер, который будет работать между двумя существующими. Таким образом, драйверу снифера не нужно работать непосредственно с железом и не нужно разбирать запросы пользователей, мы только реализуем собственные функции в нужном месте.

Формат Драйвер на самом деле имеет стандартный формат PE (Portable Executable) формат, как и у любого другого приложения. Отличие состоит в том, что расширение такого желательно установить .sys и внутри файла программист должен реализовать определенные функции, через которые будет происходить общение с операционной системой. Помимо этого могут быть пользовательские функции, которые будут решать поставленные разработчиком задачи. Каждая программа должна иметь точку входа. Программисты C++ знакомы с такой точкой хорошо и привыкли, что её имя WinMain. У драйвера такую точку называют DriverEntry. Эта процедура получает в качестве параметра два значения: 1. PDRIVER_OBJECT - Указатель на созданный драйвер. 2. PUNICODE_STRING – строка, содержащая раздел реестра, где прописан драйвер.


Конечно же, имя функции DriverEntry не является обязательным, и ты можешь её назвать по другому, но лучше следовать этому правилу, потому что читабельность кода никто не отменял. А вот количество и типы параметров менять вообще не желательно. Вот так вот может выглядеть простейший драйвер на С: #include <ntos.h> NTSTATUS STDCALL DriverEntry ( IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath) { return STATUS_UNSUCCESSFUL; } Драйверы, как и сервисы, прописаны в реестре: KEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services Здесь для каждого драйвера прописаны его параметры и ты можешь прочитать их и здесь же желательно сохранять параметры работы драйвера. Для хранения параметров необходимо использовать ветку реестра: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Имя\Parameters Где "Имя" – имя драйвера. В качестве результата функция возвращает тип данных NTSTATUS. Если результат равен STATUS_SUCCESS, то драйвер считается загруженным верно и может обрабатывать ввод/вывод информации. Иначе, драйвер не загружается в память и не может использоваться. При выгрузке драйвера вызывается функция Dispatch, которая выглядит следующий образом: NTSTATUS STDCALL DriverEntry ( IN PDEVICE_OBJECT DriverObject, IN PIRP Irp)

Ввод/вывод Драйверы ввода/вывода должны создавать логическое, виртуальное или физическое устройство, с которым будет происходить обмен ввода/вывода. Такое устройство создается с помощью функции IoCreateDevice. Описывать эту функцию я не буду, потому что она содержит аж 7 параметров и кучу особенностей. Если ты будешь писать драйвера устройств, то придется обратиться к MSDN. Для удаления устройства используется функция IoDeleteDevice. Создав устройство, можно создавать символическую ссылку на него с помощью функции IoCreateSymbolicLink. Создав ссылку с именем "xakep", драйвер будет виден в системе /device/xakep.


MSDN поможет разобраться с функцией IoDeleteDevice

Приоритеты прерываний Исполняемый код имеет определенный уровень прерывания IRQL (interrupt request levels), по которому определяется, что позволено делать, а что нет. Это не уровень потока, это уровень именно прерывания. Всего таких прерываний 32. Прерывание с нулевым номером обладает низшим приоритетом, а 31 соответственно наивысшим. Наивысшим уровнем обладают прерывания устройств. Эти IRQL соответствуют аппаратным прерываниям. Низшие два прерывания реализованы программно. Нулевой уровень зарезервирован для потока, который работает, когда системе нечего делать. Если верить DDK, то это значение указывать нельзя. Я не пробовал, и не знаю, какой будет результат, если попытаться. Уровни начиная с 1 до 15 являются динамическими, потому что по мере необходимости ОС может занижать или повышать эти значения, чтобы драйвер выполнялся как можно быстрее и не блокировал работу системы. Диапазон от 16 до 31 – фиксированные прерывания и в их уровни ОС не вмешивается. Прерывания от 16 до 31 имеют одно очень важное свойство – их работа не может быть прервана другим процессом, если его приоритет прерывания ниже. В связи с этим, такой код должен быть максимально компактным и выполняться очень быстро, дабы не монополизировать процессор. В диапазоне от 1 до 15 приоритет прерывания может изменяться, поэтому в определенный момент код с большим приоритетом может быть прерван кодом с низшим значением только потому, что ОС решила поиграть с этими приоритетами. В зависимости от уровня драйвера, он может реализовывать еще несколько функций, например, функцию InterruptService. Она обязательна для всех драйверов устройств, которые генерируют


прерывания. Специфичных для различных уровней и устройств функций много и описать их нереально. Прежде чем писать драйвер обязательно проштудируй DDK.

Отладка Вообще отладка драйверов достаточно сложное и утомительное занятие. Дело в том, что если ты допустишь ошибку в пользовательском приложении, то такую задачу достаточно просто снять, перекомпилировать и запустить по новой. С драйверами такие шутки проходят редко, потому что если в коде допущена ошибка, то велика вероятность увидеть синий/черный экран смерти. После этого компьютер спасет только перезагрузка, после которой найти проблемную строку кода не всегда возможно. При отладке можем порекомендовать следующее: 1. Сохраняй все до начала тестирования. К этому правилу ты быстро привыкнешь после пары системных сбоев и потери нескольких часов работы. 2. Используй специальную версию окон с отладочной информацией, которая поможет в поиске ошибок после сбоя. 3. Тестируй как можно чаще и как можно больше. Необходимо после каждого маленького изменения тщательная проверка работы, иначе потом исправлять ошибку будет намного сложнее.

Лучший сайт для системщика

Встроенный в IDE отладчик тут не подойдет. Необходимо что-то более серьезное, например SoftICE или Kernel Debugger из DDK. Да, второй вариант не особо удобен, потому что требует наличия двух компьютеров, но если ты используешь виртуальную машину, то все решаемо.

Проще некуда А можно сделать разработку драйвера проще? Конечно можно. Например, на jungo.com ты можешь найти DDK, при использовании которой тебе не нужно знать внутренностей ОС. Все достаточно просто, jungo уже написала универсальный драйвер, который берет на себя все сложные функции по работе непосредственно с ОС. Остается только написать надстройку, которая будет работать через этот драйвер. К преимуществам пакета от jungo можно отнести то, что разработка значительно упрощается и тебе не нужно знать DDK или внутренностей ОС. Но есть и недостаток – ограниченность возможностей. Данный пакет позволяет делать далеко не все. И все же, для простых задач и быстрого решения проблемы пакет незаменим.


Набор для разработки драйверов под окна от Jungo

[More info] В одной статье описать всю специфику драйверов невозможно. На эту тему можно писать и писать, поэтому без дополнительных источников инфы тебе не обойтись. Могу посоветовать следующие сайты: 1. http://www.wasm.ru - многие годы этот сайт остается лучшим для системщика и программиста на ассемблере. Да, информация здесь для ассемблера, но без знания этого языка отлаживать драйверы не так уж и просто. Так что настоятельно рекомендуем ознакомиться со статьями с этого сайта. 2. MSDN и справка из DDK – без знания английского можно писать только простые программы, а для драйверов хорошей информации на русском очень мало, поэтому свежачок можно найти только в файлах справки DDK и MSDN. 3. Программирование драйверов в Windows (автор: Солдатов) - одна из немногих книг по программированию драйверов на русском языке. Я эту книгу не видел, но отзывы в инет магазинах нормальные. Совсем недавно вышло третье издание этой книги.


Лучший сайт для системщика

Compiling complete Любой системный программист и тем более хакер должен четко представлять себе архитектуру Windows. Невозможно написать полноценные программы безопасности без драйверов. Например, полноценный сетевой экран или снифер невозможен на пользовательском уровне. Тут просто необходимо обращаться к более низкому уровню и писать драйвер. Можно использовать уже готовые разработки, но это не солидно для элиты, и не приемлемо для коммерческого продукта. Удачной компиляции!

By, Фленов Михаил aka Horrific E-mail: horrific@vr-online.ru WWW: www.vr-online.ru


Боевые действия в условиях ограниченной видимости Современный компьютер обладает как минимум 512 метрами памяти (плюс файл подкачки), поэтому программисты не особо заботятся об экономии оперативки. Мы выделяем под собственные нужды тонны ячеек и страниц, не обращая внимания на возможные проблемы. А ведь память не резиновая и может когда-нибудь закончиться. А бывают случаи, когда не хватит памяти даже суперкомпьютера. Если каждый будет так бездарно расходовать содержимое микросхем, то для нормальной работы не хватит и гига оперативки.

А стоит ли экономить? Даже при наличии 512 мегабайт, расходовать память, не думая о последствиях глупо. Дело в том, что Windows XP в домашней редакции уже съедает от этого объема 128 метров, а профессиональная редакция отнимает все 256. Всякие примочки и побрякушки в районе часов, антивирусы и сетевые экраны могут отнять еще 64 метра. Получается, что для других приложений остается не так уж и много места. Если одновременно будет запущен Delphi 2006, 3DS Max и Photoshop, то работа станет невыносимой, ведь эти монстры сжирают оперативку хуже вирусов. В такие моменты вспоминаются времена MS-DOS с его ограничениями и советские машины (за которыми мне удалось поработать в начале 90-х), когда приходилось экономить каждый байт памяти. Сейчас байты и тем более биты уже никто не считает, а вот кило и мега считать необходимо в любой программе, поэтому данная статья может пригодиться даже тем, кто не собирается писать программы в тяжелых условиях с ограниченным объемом памяти. Необходимо всегда расходовать предоставленные ресурсы разумно.

Массивы Самый банальный и бессмысленный расход памяти происходит благодаря использованию массивов фиксированной длины. Допустим, что тебе нужно хранить в памяти заранее неизвестное количество чисел. Как поступить в этом случае? Можно зарезервировать максимально возможный буфер и никаких проблем. Да, проблем никаких, а вот утечка памяти произойдет достаточно серьезная. Допустим, что ты выделил массив из 1024 чисел. Если каждое число типа Integer, то из-под ног утекает 4096 байт памяти. А если тип данных Int64? А если это не число, а структура, размером в 100 байт? Это уже сто килобайт памяти. Десять таких массивов и драгоценный мегабайт уходит в небытие. Простейший вариант решения проблемы – в массиве хранить не сами структуры, а указатели. В этом случае размер массива значительно сокращается, но он все равно не оптимален. Мы резервируем 1024 элемента, а реально в программе может использоваться только один. Идеальное решение – использование динамических массивов. Не стоит бояться динамики, она безобидна по сравнению с Фредди Крюгером. Можно выделить два основных типа динамических массива: 1. Просто выделяем память для хранения элементов массива, а по мере необходимости память расширяется. Данный вариант самый простой и самый компактный, но очень неудобный, когда необходимо изменять последовательность элементов, например, для сортировки, или удалять элементы из середины массива.


Исходник примера с динамическим массивом

2. Можно написать класс, который будет управлять динамическим массивом. Простейший вариант такого класса я набросал в листинге 1. Данный код упрощен до минимума. На компакт диске ты найдешь более полный вариант с примером использования. Да, такое хранение данных требует некоторой избыточности для каждого элемента, но зато наш массив полностью динамичен и занимает в памяти ровно столько, сколько необходимо. Ничего лишнего резервировать ненужно. А главное, размерность массива ограничена размером типа данных, который используется для хранения количества элементов.

Окно примера работы с динамическими массивами, который ты найдешь на диске


Представление связанного списка в памяти. Стрелками показано, как наведена связь, между элементами

Как вариант, для хранения динамического массива указателей можно использовать класс TList (в данном случае совет относиться к программистам Delphi). Лично я регулярно использую этот класс, потому что он прост и удобен, но иногда пишу свою реализацию динамики, все зависит от задачи. На самом деле алгоритмов создания списков очень много, но я выделяю эти два, как наиболее эффективные и достаточно простые в реализации. В зависимости от задачи можно придумать и другой алгоритм, все зависит от того, как хранятся данные, нужно ли их сортировать, есть ли необходимость удалять из середины, куда добавляются новые элементы – в конец или могут вставляться в середину списка и т.д.

Пока чуть-чуть Допустим, что ты пишешь программу воспроизведения/записи звука или видео. На первый взгляд, для воспроизведение mp3 файла необходимо столько же памяти, сколько данные занимают в файле. Достаточно только загрузить все память и отправить данные звуковой карте. Ошибочка вышла. Не факт, что звуковая карта сможет понять сжатые данные. Придется производить декомпрессию самостоятельно и направлять звуковой карте не сжатые данные. Если разложить по полочкам mp3 файл, то для хранения всех данных может понадобиться 60, а то и все 100 мегабайт памяти. Я думаю, за такое расточительство тебя ни один пользователь не простит. Еще один пример – простой ZIP архиватор. Что если необходимо сжать файл размером в 500 метров? Загрузить его в память, а потом заархивировать его там будет достаточно проблематичным. Во всех этих случаях вполне логичным будет выделить два небольших участка памяти. В первый необходимо загружать данные из файла для последующей манипуляции (компрессии/декомпрессии), а во второй будет помещаться результат работы. Тут нужно быть аккуратным, потому что размеры сжатых и не сжатых данных разные и легко выйти за границу буфера. Не стоит пытаться запихнуть в оперативку все сразу, потому что это бессмысленно. Загружайте всегда данные небольшими порциями.

Циклическое использование памяти При работе со звуком Microsoft уже предоставляет нам очень удобные механизмы работы с памятью, которые мы сейчас рассмотрим. Возможно, данный алгоритм ты будешь использовать в своих программах при решении других задач. Итак, MMSystem получает при воспроизведении и возвращает при записи звуковые данные через специализированные буферы. Так как данные загружаются порциями и для


предотвращения задержек в воспроизведении/записи музыки, используется очередь из буферов. Для нормальной работы достаточно двух блоков памяти, но можно сделать и больше.

Циклическое использование памяти

Для примера, выделяем память для хранения двух буферов данных. Размер памяти можно выбрать любой, но он должен быть не слишком маленьким, дабы избежать слишком частой смены буферов и не слишком большой, чтобы сэкономить память для других целей. Точный размер подбирается в зависимости от качества звука, потому что от этого зависит и размер необходимых данных. Из личного опыта я всегда выделяю столько, чтобы хватило на воспроизведение/запись не менее 5 и не более 10 секунд звука. Теперь, говорим звуковой карте, что нужно использовать буфер 1, а буфер 2 помещаем в очередь. Так как MMSystem работает в отдельном потоке, ждем, когда буфер 1 освободиться. Когда это произойдет, система переключиться на следующий буфер в очереди (буфер 2), и пока она будет с ним работать, мы освобождаем буфер 1 или заполняем его новыми данными, и снова помещаем в очередь. Таким образом, с помощью цикла можно по частям обработать данные любого размера с минимальными расходами памяти. Два буфера в данном случае минимально необходимы, но желательно использовать 3 или 4. Дело в том, что в момент освобождения буфера, система может быть занята и не успеет его очистить, подготовить и вернуть обратно в очередь. В этом случае может произойти заикание в воспроизведении или даже сбой, если идет запись.

Сортировка Когда данных очень много, то просматривать их все очень сложно и проблематично даже для очень мощного процессора, если памяти не достаточно. Дело в том, что процессор – не самое главное в современном компьютере. Слабым местом по-прежнему остается жесткий диск. Я уже много раз говорил о том, что это механика и, не смотря на то, что производители пытаются из нее выжать максимум, она остается очень слабой. Можно попытаться оптимизировать процесс поиска данных через дефрагментацию, дабы головка жесткого диска не прыгала по блину как угорелая, а читала их последовательно, но


затраты в соотношении с результатом слишком большие. Можно читать данные большими кусками, а обработку перенести в отдельный поток, дабы максимально распараллелить задачи, но это тоже не совсем то. Если данных действительно очень много, а памяти очень мало, в бой вступают мозги и алгоритмы. Мы должны реализовать алгоритм так, чтобы не было необходимости просматривать все данные. И в этом нам помогут – сортировка и индексация. Давай посмотрим, как они могут нам помочь. Для начала возьмем сортировку. Когда данные упорядочены, с ними проще работать и искать необходимую информацию. Допустим, что у нас есть список всех жителей города и тебе нужно найти номер телефона Иванова. С такой фамилией в целом городе может быть не один человек, а сотня. Просматривать весь список достаточно проблематично, а если он будет упорядочен по фамилии, то достаточно будет просмотреть все записи на букву "И" и готово. Экономия времени и памяти на лицо.

Индексация Поддерживать списки в упорядоченном виде достаточно проблематично, особенно, если записи очень часто изменяются, добавляются, удаляются и при этом каждая из них может иметь произвольный или очень большой размер. В этом случае записи хранят на диске в произвольном виде, а для быстрого поиска используют индексы. Так поступает большинство баз данных, дабы сэкономить память и процессорное время. Индексы – могут состоять из двух колонок: поле, по которому происходит упорядочивание данных и указатель на место, где находятся данные этой записи. Сами данные хранятся в произвольном порядке, а вот индексная табличка сортируется. Так как она очень маленькая и состоит только из двух полей, такую табличку достаточно легко поддерживать в упорядоченном виде, а для хранения ее в памяти нужно намного меньше места, чем для хранения всего списка. Теперь, если нам снова нужно найти номер телефона по фамилии, мы бежим по индексной таблице и ищем необходимую фамилию. Когда она найдена, переходим по указателю из индексной таблицы и получаем данные человека, в том числе его номер телефона и адрес. Индексные таблички хороши, но только в меру. Для телефонного справочника, может понадобиться три индекса: для фамилии, телефона и адреса. Благодаря трем индексам мы можем легко упорядочить данные по любому из этих параметров, но содержать в упорядоченном виде придется уже три таблички, а это уже сложнее.

Деревья Индексы – очень мощное средство для экономии памяти и упрощения сортировки. Но для поиска данных все равно приходиться сканировать всю таблицу, пока мы не найдем необходимую запись. Если искомый текст начинается на букву "А", то мы найдем его быстро, ведь он будет находиться в самом начале. Но если искомая строка начинается на "Я", то во время поиска мы придется сканировать до самого конца и экономия скорости будет небольшой. Да, мы все еще экономим память, потому что загружаем в оперативку только индексную таблицу, а не все данные, но скорость все еще не высокая. А если построить индекс в виде дерева, то мы сэкономим и место и время. Индексы в виде деревьев очень часто используются в базах данных, в том числе и в MS SQL Server. Так что, понимание этого индекса пригодиться не только программистам (для


реализации в собственных проектах), но и администраторам, для лучшего понимания внутренностей баз данных.

Пример древовидного индекса

Индекс не зря называют деревом, потому что все данные в нем располагаются именно в таком виде. Давай рассмотрим, как выглядит дерево в памяти, и ты сразу увидишь все преимущества и недостатки. Я попытался его изобразить графически, но художник из меня не очень хороший (это если мягко выразиться). Во главе всего находиться главный блок, с которого всегда начинается поиск. На рисунке я набросал дерево, состоящее из первых попадавших в мою голову слов. Да, сегодня в моей голове явно бред, пора бы отдохнуть.

Прогулка по дереву Итак, дерево состоит из блоков равного размера. Конечно же, размер блока должен быть достаточен для хранения хотя бы нескольких записей, но он и не должен быть слишком большим. Некоторые специалисты рекомендуют делать его равным 8 кило (мой любимый размерчик!). Не знаю, откуда взялась эта рекомендация, но ей можно найти логическое объяснение. Блок получается не большой и не маленький, а в самый раз. Внутри блока все строки желательно упорядочить, дабы упросить в нем поиск. Так как блоки у нас небольшие, то поддерживать все записи в нем в упорядоченном состоянии достаточно просто. Попробуем найти слово "Гусь". Начинаем поиск с первого блока и последовательно просматриваем все строки. Находим слово "Бутылка", которое меньше, чем искомый гусь, но следующее слово (Пена) будет уже больше. Переходим в следующий блок, на который указывает найденная бутылочка. Ищем здесь и видим, что ближайшее к искомому слово – Груша. Переходим по ссылке на следующий блок, где и как раз и есть искомый Гусь.


B-Tree деревья в Oracle. На кончиках веток находятся значения индекса и ROWID

Ощутил преимущества древовидного индекса? Если нет, то вот они: 1. Для поиска даже самой последней записи в наборе данных, не нужно просматривать абсолютно все блоки. Вполне возможно, что достаточно будет просмотреть только 3 или четыре индексных блока; 2. На поиск любых данных затрачивается примерно одинаковое время; 3. Для эффективной работы вполне достаточно столько памяти, сколько занимает один индексный блок. Все блоки держать в оперативке нет смысла, но если есть лишняя память, то можно кешировать блоки; Недостаток, на мой взгляд, только один, но он очень серьезный – поддерживать такой индекс не так уж и просто. О возможных вариантах поддержки можно писать отдельную статью. Я же могу порекомендовать тебе прочитать что-нибудь про архитектуру индексов в базах данных Oracle и MS SQL Server. Архитектуру первой я знаю не очень хорошо, а вот в MS SQL Server индексные деревья сделаны достаточно эффективно.

Отличная книга по Oracle, в том числе и по его архитектуре


Вот так вот при минимуме затрат можно быстро искать и обрабатывать большие объемы информации. В данном случае, мы затронули разновидность лиственных под называнием BTree (Balanced Tree или сбалансированные деревья). Бывают и другие разновидности, но это уже совершенно другая история.

Кластер или нет Существует две разновидности деревьев – кластерные и не кластерные. В первом случае, на кончиках веток индексного дерева находятся сами данные. Это значит, что прогулявшись по всем блокам мы находим непосредственно искомые данные. Получается, что в данном случае данные упорядочены с помощью такого индекса физически. Конечно же, на один список (таблицу) можно создать только один кластерный индекс, потому что упорядочить физически по двум разным параметрам один и тот же набор данных просто нереально.

Классическая книга для любителей алгоритмов – Искусство программирования

В не кластерном дереве на кончиках веток находятся только ссылки на данные, а сами данные могут находиться где угодно на диске и в любом порядке. Таких индексов можно создавать сколько угодно.

Итого Мы поделились с тобой только основными алгоритмами экономии памяти и немного затронули даже оптимизацию. На эту тему можно говорить вечно, потому что оптимизация скорости и памяти – очень интересны сами по себе. Мы же не будем углубляться дальше основ, потому что дальнейшая оптимизация скорости и ресурсов требует хорошего знания математики. Если у тебя нет проблем с этой наукой, то за новыми алгоритмами можешь обратиться к трудам Кнута. Да, тексты его книг завернуты не по детски и без хорошего знания высшей математики и бутылки водки тут не разобраться, но зато пищи для ума хватит на год вперед. Листинг 1. Пример динамического массива type PMassivItem = ^TMassivItem; TMassivItem = record nextItem:PMassivItem; //следующий элемент массива prevItem:PMassivItem; //предыдущий элемент массива Name:String; // пример данных, которые нужно хранить // здесь могут быть еще поля элемента массива end; TMassiv = class


public FirstItem:PMassivItem; // первый элемент списка LastItem:PMassivItem; // последний элемент списка itemsNumber:Integer; // количество элементов constructor Create(str:String); // конструктор procedure AddItem(str:String); // добавление элемента procedure DeleteItem(index:Integer); // удаление end; procedure TMassiv.AddItem(str:String); var newItem:PMassivItem; begin newItem:= new(PMassivItem); newItem.Name:=str; newItem.nextItem:=nil; newItem.prevItem:=LastItem; LastItem.nextItem:=newItem; LastItem:=newItem; Inc(itemsNumber); end; constructor TMassiv.Create(str:String); begin itemsNumber:=1; FirstItem:=new(PMassivItem); FirstItem.Name:=str; FirstItem.nextItem:=nil; FirstItem.prevItem:=nil; LastItem:=FirstItem; end; procedure TMassiv.DeleteItem(index: Integer); var i:Integer; currentItem:PMassivItem; begin // поиск удаляемого элемента currentItem:=FirstItem; for i:=0 to index-1 do currentItem:=currentItem.nextItem; // наводим новые связи и освобождаем память if currentItem.prevItem<>nil then currentItem.prevItem.nextItem:=currentItem.nextItem; if currentItem.nextItem<>nil then currentItem.nextItem.prevItem:=currentItem.prevItem; Dispose(currentItem); //очистка памяти Dec(itemsNumber); //уменьшаем счетчик end; Исходник примера ищи в архиве massive.zip

By, Фленов Михаил aka Horrific E-mail: horrific@vr-online.ru WWW: www.vr-online.ru


Базы данных

От редактора: Если ты работаешь программистом, то я более чем уверен, что тебе приходится сталкиваться с базами данных. Программирование/администрирование баз данных процесс трудоемкий и требует наличия определенных знаний. Как и любые знания их нужно периодически пополнять. Вот как раз в пополнении знаний в области БД тебе и поможет данная рубрика в этом номере.


Postgre-SQL в Linux С недавнего времени я перешел с ОС Windows на Linux. Задумал я это сделать давно, еще когда учился на первом курсе университета, если кому интересно Ижевского Государственного Технического Университета. Но как всегда бывает у всех то сессия, то еще какие то проблемы. Некоторые среды разработки которые мы проходили были просто напросто реализованы только под Windows. Из-за этих множественных факторов переход на бесплатную ОС все затягивался и затягивался. Вот моя первая статья из этого раздела. Буду надеется я на ней не остановлюсь и буду дальше рассказывать как я осуществляю плавный переход на OS Linux.

Базы данных в *nix системах. PostgreSQL Сперва чтобы познакомится и ощутить мощь программы нужно её скачать и это сделать можно на на официальном сайте www.postgresql.com. Сейчас доступна для скачивания версия 8.2.4. Весит примерно 12 мегабайт. Мои изыскания были произведены на версии 7.1.3. Принцип установки остался прежним так что можете смело качать новую версию и читать мою статью дальше. Скачали? Молодцы и теперь приступим к установке сего пакета. Процесс установки стандартен можно даже сказать бонален для пользователей ОС Linux. Как обычно вызываем терминал бесхитростными манипуляциями мышки. Запустили? OK. Тогда пишите следующие строки. gunzip postgresql-7.1.3.tar.gz tar -xvf postgresql-7.1.3.tar cd Postgres-7.1.3 ./configure gmake gmake install После установки нам понадобится новый пользователь с именем Postgres. Создадим его следующей командой. adduser Postgres Все пользователь Postgres был создан. Теперь зайдем под учетной записью этотго пользователя. su Postgres

Сфера использования баз данных очень широка - от рабочих станций и корпоративных сетей до internet. Cам по себе Postgres - сетевое приложение, доступ к которому можно получить не только с локальной машины (AF_UNIX), а и с удаленной (AF_INET). По умолчанию используется порт с номером 5432, но можно установить и другое значение, используя

параметр "-p <port>" при запуске сервера (postmaster). У пользователя есть возможность взаимодействовать с базой данных как посредством командного интерпретатора postgresql. Это можно сделать запустив /usr/local/pgsql/bin/psql <database name>), так и путем написания скрипт-приложений на языках Perl, PHP, C и т.д. Я


воспользуюсь Perl скриптами. Нужен модуль DBI для работы с Postgres. Скачайте его с сайте www.perl.org. gunzip DBD-Pg-1.01.tar.gz tar -xvf DBD-Pg-1.01.tar cd DBD-Pg-1.01 perl Makefile.PL make make install Вручную нужно прописать переменные окружения: export POSTGRES_LIB=/usr/local/pgsql/lib export POSTGRES_INCLUDE=/usr/local/pgsql/include Теперь Ваш перл понимает "use DBD". Интерфейс DBI сам по себе он очень напоминает ODBC - довольно удачная реализация работы со множеством форматов БД. Это позволит нашим скрипт-приложениям работать с практически любыми базами данных, которые на данный момент только существуют, для этого Вам придется скачать необходимый DBI модуль. Ну, перейдем от теории к практике. Для нормальной работы нам необходимо запустить сам демон Постгреса. /usr/local/pgsql/bin/postmaster -D /usr/local/pgsql/data >logfile 2>&1 & Ключом "-i" для активизации возможности создания TCP/IP соединений. Параметр "-D" указывает расположение директории с базами данных. Использование параметра "-p" позволит Вам вручную указать номер порта, который будет использовать демон для создания сетевых соединений, а "-d" - указать уровень отладки (debugging level). Более полное описание Вы можете получить, используя ключ "--help". Сама директория создается так: /usr/local/pgsql/bin/initdb -D /usr/local/pgsql/data Создадим саму базу данных. Для этого введем: /usr/local/pgsql/bin/createdb <database name> В примере будет использоваться имя БД "database". Для входа в SQL консоль наберем команды: /usr/local/pgsql/bin/psql database Для дальнейшей работы нам понадобятся знания запросов SQL. В консольном режиме работа с Postgres практически ничем не отличается от работы с MySQL. Первоочередная задача при работе с БД - создание таблиц. Для этого предусмотрена команда create table <table name> values (<fields>);


fields представляет собой набор полей базы. При объявлении поля, необходимо указать его имя и типа (например surname varchar(35) – под поле "surname" резервируется строка из 35 символов). Просмотреть ново созданную таблицу можно воспользовавшись командой \d <table name>, которая выведет содержимое таблицы <table name> . Эта же команда без параметров приведет к выводу на терминал названий всех таблиц в базе данных (аналог "show tables;" в MySQL). Помимо "\d" среда Postgres предоставляет и другие не SQL команды. \q - выход из среды. \h - вывод помощи по SQL командам. \? - вывод помощи по "\*" командам. \i <file> - загрузить и выполнить запросы, содержащиеся в файле <file>. \p - показать буфер запросов. \r - очистить буфер запросов. \l - показать список баз данных (аналог "show databases;" в MySQL). \z - показать разрешения на доступ к базам данных. \s <file> - показать history или сохранить ее в <file>. \set <var> <value> - установить внутреннюю переменную. \df(do) вывод на консоль (терминал) доступных названия, параметры, возвращаемое значение, описание.

функций/операторов-

Теперь разберемся с наполнением нашей базы данных. Для добавления записей в Postgres предусмотрена команда INSERT. Пример использования insert: insert into data values ('Konstantin' , 'Selivanov'); В таблицу data (name varchar(n1), surname varchar(n2)) будет добавлена запись "Konstantin Selivanov". Для просмотра содержимого таблиц в СУБД Постгрес предусмотрена команда select.

Синтаксис: SELECT <условие> FROM <таблица> WHERE <поле>{=,>,<,...}<значение>; Пример запрос "select * from data where name='Konstantin'" приведет к выводу на терминал подобной таблицы:

+---------------+-------------+ | name | surname | +---------------+-------------+ | Konstantin | Selivanov | +---------------+-------------+ 1 row is set ( 0.02 sec) Удаление записи также не является сложной задачей: DELETE FROM <таблица> WHERE <поле>{=,>,<,...}<значение>; Пример использования delete:


DELETE FROM data WHERE surname='Selivanov'; Из таблицы data будут удалены все записи, поле surname которых содержит значение Selivanov. Пример простого скрипта, выводящего пользователю все содержимое базы database таблицы data : #!/usr/bin/perl use DBI; # Подключение DBI модуля $dbn = "database"; $user = "Postgres"; $table = "data"; # указываем параметры для установки скриптом соединения с базой print "Content-type: text/html\n\n"; print "<HTML>"; print "<BODY>"; print "<LI>Соединение с базой данных...\r\n</LI>"; $conn = DBI->connect("DBI:Pg:dbname=$dbn","$user","") or die "Ошибка соединения."; # вот мы и соединились. $req = "SELECT * FROM $table;"; # формируем SQL запрос $result = $conn->do($req); # в переменную $result запишется количество записей в таблице , # полученное в результате выполнения сформированного запроса. $strin = $conn->prepare($req); @cont = $strin->execute; # выполним сам запрос print "<P>В базе данных $dbn в таблице $table было найдено $result записи(ей).</P>\n"; print "<P>Они и представлены Вашему вниманию:</P>\n"; print "<table border=1>\n"; while($i<$result) # для всех записей в таблице { @row = $strin->fetchrow_array; # используя соединение $conn получим содержимое первой записи print "<tr><td>$row[0]</td><td> $row[1]</td><td> $row[2]</td><td>$row[3]</td><td></tr>\n"; # выведем полученные данные . $i++; } print "</table>\n"; @cont = $strin->finish; # завершим работы с созданным соединением $rc = $conn->disconnect; # отсоединимся от базы данных print "</BODY>"; print "</HTML>"; Теперь пользователь получит у себя на экране содержимое базы database. Как видим, синтаксис напоминает работу с MySQL.


Этого же результата можно было достичь, введя в Postgresql консоли SQL запрос "select * from data;". При выводе информации из базы данных существует возможность определения порядка выводимых записей. Для этого используется конструкция ORDER BY. Пример выполнение запроса: "SELECT * FROM DATA ORDER BY NAME;" приведет к выводу содержимого таблицы data в порядке алфавитного следования полей name. Еще одну интересную возможность представляет использование оператора case. Пример: select name , case when surname!='Ivanov' then 'not Ivanov' else 'this is Ivanov' end from data; Результатом будет вывод на терминал 2-х столбцов. 1-й столбец - 'name', 2-й - столбец 'case'. При этом в столбце case для тех записей, поле surname которых содержит значение Ivanov будет содержать текст 'this is Ivanov', в противном случае - 'not Ivanov'. Конечно, при этом допустима не только операция проверки на равенство, но и другие операции. Иногда, у пользователя возникает необходимость не только вывести не терминал содержимое определенных записей таблицы, а и произвести подсчет количества таких записей, среднее значение по какому-либо параметру. Для этих целей в Постгресе предусмотреные специальные функции: COUNT/MAX/MIN/AVG/SUM. Например, выполнение запроса: SELECT COUNT(*) FROM table; Даст пользователю информацию о количестве записей в таблице, запрос SELECT SUM(money) FROM bank WHERE id <=50; подсчитает количество денег на банковских счетах первых 50-ти клиентов. Следует отметить еще одну особенность таблиц - каждая запись в БД имеет свой собственный уникальный номер, называемый oid. Проверить это утверждение можно простым запросом: SELECT oid FROM table;

Вложенные запросы. Постгрес дает пользователю интересную возможность - использование вложенных запросов (subqueries). Вложенность запросов - довольно гибкий механизм, позволяющий более надежно производить манипуляции с записями БД. Приведем пример вложенного запроса: SELECT * FROM stuff WHERE surname<>(SELECT surname FROM friends WHERE id=8); Результатом обработки такого запроса будет вывод на терминал информации из таблицы stuff, для которых поле surname отлично от поля surname записи с id=8. Таким образом, в случае если содержимое записи с id=8 будет изменено, это не отразится на корректной работе запроса, так как изменения будут учтены динамически. Следует заметить, что при таком использовании вложенности, необходимым условием является возвращение вложенным запросом внешнему лишь одного значения.


Все преимущества subqueries видны при использовании операций IN/ALL/ANY/EXISTS. Это дает возможность с легкостью находить совпадения данных в таблицах, анализировать наличие или же отсутствие таковых и многое другое. SELECT * FROM data1 where tel in (SELECT tel FROM data2); Выполнение этого запроса приведет к выводу тех записей из data1, поля tel которых присутствуют в таблице data2. EXISTS вернет true, если подзапрос возвращает хотя бы 1 запись, ANY - если переменная равна какому-либо значению из списка, ALL - если условию для переменная удовлетворяют все значения.

Заключение Еще можно много и долго говорить о PostgreSQL, но на сегодня хватит. Постгрес - уже не просто конкурент MySQL. На данный момент это нечто вроде альтернативы, при чем альтернатива это вполне достойна занять свою нишу в доле современных баз данных. Разработка этой СУБД - плод многолетней работы множества талантливых программистов, в связи с чем нельзя не относиться серьезно к такому программному продукту.

By, Селиванов Константин aka KeyWareZ E-mail: KeyWareZ@yandex.ru


Совместный доступ к данным Чем больше количество одновременно работающих с базой данных пользователей, тем больше вероятность встретиться с конфликтом одновременного редактирования одной и той же строки. Что делать серверу, если два пользователя одновременно пытаются обновить одну и ту же запись? Можно принять то изменение, которое пришло позже, но тогда один из пользователей будет видеть у себя некорректные данные. А самое страшное, он будет думать, что все в порядке. Как разрешить подобные проблемы? Допустим, что два пользователя открыли для редактирования форму с одним и тем же документом. Первый пользователь изменяет важные параметры, цены, количество и нажимает сохранить. Другой пользователь не видит этих изменений, потому что он получил данные раньше. При этом, он изменяет только незначительный параметр, например, дату и тоже нажимает сохранение. Незначительное изменение второго пользователя перезаписывает важные изменения первого. В многопользовательских приложениях, к программированию можно поступать двумя способами: 1. Кто последний, тот и прав. В этом случае вы просто реализуете логику программы и не заботитесь о том, что два пользователя могут одновременно изменять какие-то данные. Прав будет тот, кто чуть позже нажмет кнопку обновления. 2. Попытаться реализовать одновременный пользователь собственными средствами, с помощью журналов. Если в журнале есть запись, что кто-то открыл документ, но не закрыл, то не разрешать повторное открытие другим пользователям. Может быть, где-то это будет удобно и быстро, но в Oracle реализованы хорошие встроенные средства блокировок, которые работают быстрее, эффективнее и надежнее, поэтому данный метод мы не рекомендуем к использованию. 3. Блокировать записи, которые пользователь собирается изменять средствами базы данных. Заблокированную запись невозможно изменить, поэтому все запросы на редактирование будут отклоняться. Данный подход является более правильным и именно ему посвящена данная статья. Заблокированные средствами Oracle записи может изменить только тот пользователь, который установил блокировку. Остальные могут только просматривать и не могут выполнять над этими данными операторы UPDATE или DELETE.

Блокировка Блокировка – это механизм базы данных, с помощью которого сервер удерживает определенные ресурсы за определенным пользователем. Остальные пользователи могут только читать заблокированные данные. Oracle достаточно интеллектуален и блокирует данные на максимально необходимом уровне. Если изменению подвергается только одна строка, то только она будет удержана. Не все такие интеллектуальные и другие базы данных могут блокировать данные целыми страницами, а в одной странице может быть несколько строк, и все они становятся недоступными для редактирования. Если вы используете другую базу данных, то сначала познакомьтесь с ее механизмом работы. Блокировки бывают явными и не явными. Неявные создаются сервером без нашего участия при каждом изменении данных таблицы или структуры и снимаются по завершению выполнения оператора. Такие блокировки существуют только во время выполнения операции модификации данных.


Явные блокировки задаются пользователем и могут быть созданы на этапе выбора данных, и будут существовать, пока пользователь не завершит работу с данными. В данном случае, вы явно указываете, что определенный ресурс должен быть закреплен за вами до тех пор, пока вы его не отпустите. Не стоит бояться блокировок, потому что в Oracle они никак не сказываются на производительности системы. Они лишь говорят о том, что какие-то данные взяты определенным пользователем для редактирования.

FOR UPDATE Когда мы просто используем оператор SELECT для выборки данных, то сервер выполняет наш запрос без блокирования каких-либо записей. Но если необходима выборка данных непосредственно для редактирования, то мы должны сообщить серверу, о блокировке. Для этого в конец запроса необходимо добавить FOR UPDATE. Например, следующий запрос выбирает все записи из таблицы Users для редактирования. SELECT * FROM Users FOR UPDATE Данный запрос ужасен, но он является только примером. Дело в том, что запрос выбирает все записи из таблицы, а значит, все они будут заблокированы для других пользователей. Никогда не поступайте так. Если вам необходимо изменить всю таблицу, то можете сразу выполнять оператор UPDATE в определенной транзакции, выбирать данные тут не имеет смысла. Если хотя бы одна строка окажется закрепленной за каким-то пользователем, то оператор UPDATE не пройдет и блокировка не поможет. Заблокировать таблицу можно еще с помощью оператора LOCK TABLE, но лучше все же выбирать с помощью запроса SELECT только те данные, которые нужны, и при этом указывать ключевые слова FOR UPDATE. Чаще всего работа с данными построена по принципу: окно реестра – окно редактирования. Например, у вас есть окно реестра документов, где пользователи могут просматривать счета, накладные и т.д. за определенный период времени. В этом окне происходит только просмотр, поэтому для выборки данных здесь не следует использовать блокировок, иначе это приведет к проблемам при многопользовательской работе. Если один пользователь выберет все документы за месяц, то остальные не смогут открыть данные за тот же период. Когда пользователь решил отредактировать какой-либо документ, то следует открыть отдельное окно, в котором будет выбран именно этот документ и на него будет установлена блокировка, например: SELECT * FROM Docs WHERE PrimaryKey=10 FOR UPDATE В этом примере мы выбираем и блокируем запись из таблицы Docs с первичным ключом равным 10. Блокировка будет поставлена только на одну запись и этот документ больше никто не сможет открыть. Так как в окне реестра документов выполняется запрос SELECT без FOR UPDATE, то он продолжит работать, и остальные пользователи смогут его просматривать и открывать для редактирования другие документы, но только не заблокированные.


Не ждите А что произойдет, если пользователь попытается открыть документ, который уже заблокирован другим пользователем? Ответ прост - запрос зависнет в ожидании освобождения ресурсов. Если в вашей программе не будет предусмотрено возможности прерывания запросов, а блокировка оказалась мертвой, то программа зависнет навечно. Завершить работу можно будет только прерыванием процесса. Самое страшное, если какой-то пользователь открыл окно и ушел на обед. Ресурс оказывается заблокированным надолго, и это мешает работе других пользователей. Если процесс прерывается аварийно, то все заблокированные этим пользователем ресурсы блокируются намертво. Чтобы их освободить, необходимо подключиться к серверу с правами системного администратора и завершить сессии. Чтобы сессия не зависла из-за бесконечного ожидания заблокированных данных, я рекомендую добавлять еще опцию NOWAIT: SELECT * FROM Docs WHERE PrimaryKey=10 FOR UPDATE NOWAIT Такой запрос попытается получить данные и установить на них блокировку, но если это невозможно, то ожидания не будет. Сервер просто вернет ошибку с номером ORA-00054: ORA-00054 Resource busy and acquire with NOWAIT specified

Попытка получить заблокированные данные


Описание ошибки ORA-00054

Теперь, когда мы увидели, что данные заблокированы, можно показать пользователю сообщение о том, что кто-то уже редактирует таблицу и открыть карточку документа, но только в режиме редактирования. Только для этого нужно выполнить снова запрос SELECT без попытки блокирования ресурсов.

Пример Давайте посмотрим, как реализовать возможность открытия карточки редактирования с использованием блокировок на Delphi. Допустим, что у нас есть форма TSomeDocument для редактирования и данные выбираются с помощью компонента TOracleDataSet (назовем его odsDocs) из состава DOA (Direct Oracle Access, прямой доступ к Oracle). В компоненте odsDocs прописан запрос на выборку данных, без каких либо блокировок. По событию OnShow для формы пишем код, показанный в листинге 1. Давайте разберем содержимое представленного листинга. Сначала сохраняем запрос, который прописан в компоненте и после этого, добавляем к запросу опции FOR UPDATE NOWAIT. Теперь открываем набор данных внутри блока try...except. Если код отработал нормально, то ресурс свободен и уже заблокирован под нами. Нужно только проверить количество записей на 0. А вдруг пока мы работали с выборкой в реестре документов, данный документ уже кто-то удалил? Если во время открытия набора данных произошла ошибка из-за блокировки, то выполнение программы переходит на блок except. Здесь возвращаем сохраненный запрос в компонент odsDocs, сообщаем пользователю, что данные невозможно открыть для редактирования, и открываем набор данных, но уже без опции FOR UPDATE NOWAIT. Это достаточно простой, но эффективный способ блокирования документов.

Блокировки в связанных запросах


А теперь посмотрим еще один интересный эффект. Допустим, что у нас есть две таблицы Docs и Users. В таблице Docs есть поле UserID, где сохраняется первичный ключ из таблицы Users. Таким образом, каждый документ привязан к определенному пользователю, например, создавшему, ответственному или кому-то еще. Посмотрим, как будет выглядеть запрос на выборку данных для редактирования: SELECT * FROM Docs d, Users u WHERE d.PrimaryKey=10 AND d.UserID =u.PrimaryKey FOR UPDATE В результате блокировка будет установлена не только на выбранный документ под номером 10, но и на запись в таблице Users, которая связана с данным документом. Это очень плохо. Теперь, если кто-то другой попытается открыть на редактирование другой документ, но тоже связанный с этим пользователем, то сервер не даст этого сделать. Все документы пользователя будут заблокированы, а это неправильно. Блокироваться должен только определенный документ, а таблица пользователей не будет редактироваться (из нее только выбирается запись) и ее сервер не должен трогать. Как сообщить Oracle, что записи в Users блокировать нельзя? Для этого нужно явно указать таблицу, а лучше первичный ключ в этой таблице: FOR UPDATE OF имя поля. После ключевого слова OF указывается поле, по которому сервер узнает, какую запись из связанных таблиц нужно заблокировать. Получается, что наш запрос должен выглядеть следующим образом: SELECT * FROM Docs d, Users u WHERE d.PrimaryKey=10 AND d.UserID =u.PrimaryKey FOR UPDATE OF d.PrimaryKey Вот теперь будет заблокирована только одна запись документа и только из таблицы Docs.

Продолжительность Чтобы пользователи не блокировали данные на слишком продолжительное время, при открытии формы можно запускать таймер и через пять минут спрашивать подтверждения продолжения работы. Если пользователь не подтвердит, то форма должна закрыться и освободить ресурс. Это поможет, если кто-то забывчивый уходит на обед или домой с запущенной программой и открытыми ресурсами. Если у вас многооконная система и предусмотрена возможность открытия сразу множества документов, то пользователь может забывать закрывать окна редактирования и это снова лишние, а главное – неоправданные блокировки. Не помешало бы средство, которое отключало бы таймер на те случаи, когда пользователь действительно хочет работать с данными долго, но он должен это делать осознано. Нет, в блокировках нет ничего страшного для сервера, производительности и корректности данных, но документ может понадобиться другим.

Система


Теперь поговорим о системных представлениях, с помощью которых вы можете управлять и контролировать блокировки. Все блокировки можно получить с помощью представления v$lock: SELECT * FROM v$lock Результат не очень информативен, потому что содержит какие-то адреса и цифры, да и записей очень много. В поле sid находиться идентификатор сессии, а в поле Type можно увидеть тип блокировки. Когда вызывается SELECT FOR UPDATE, то создается блокировка транзакции, а в поле Type можно будет увидеть TX. Существуют и другие типы, например, блокировки сервера, изменения структуры таблиц и т.д. Более подробно, об этом можно прочитать в документации по Oracle. Исходя из вышесказанного, более информативным будет следующий запрос: SELECT s.username, l.* FROM v$lock l, v$session s WHERE l.TYPE = 'TX' and l.sid=s.sid Здесь мы связались с представлением v$session, которое возвращает сессии и теперь в результат попадает имя пользователя, который удерживает блокировку. Из представления v$session можно получить много полезной информации. Просто выполните следующий запрос, чтобы определиться, какие еще поля можно включить в запрос, показанный выше: SELECT * FROM v$session Пока все хорошо, но по полученным данным мы до сих пор не знаем, кто же именно заблокировал определенную строку. Вот тут я бы порекомендовал создать пользовательскую таблицу журнала из следующих полей: • • •

идентификатор документа; идентификатор пользователя; дата.

Теперь, после каждого удачного открытия ресурса на редактирование, можно занести запись в эту таблицу с указанием документа, пользователя и даты. В программе, в окне просмотра реестра, можно реализовать функцию просмотра журнала по определенному документу. Теперь, если что-то не открывается на редактирование, то с помощью журнала пользователи сами смогут узнать, кто последний заблокировал запись и не дает работать другим. Журнал позволит избежать вам множества звонков с вопросами, кто и что заблокировал. Если злополучного пользователя нет, то тогда уже будут обращаться к вам, а вы с помощью таблиц v$lock и v$session сможете увидеть блокировки и снять их.

LOCK TABLE Допустим, что нам нужно произвести несколько действий по изменению данных во всей таблицы (или в большинстве ее записей), и все эти изменения невозможно уложить в один единственный запрос UPDATE, да и сама работа с данными отнимет не пять минут. При выполнении одной операции UPDATE блокировать таблицу не имеет смысла, но при больших изменениях просто необходимо. Если после выполнения некоторых действий, кто-то


заблокирует хотя бы одну запись, дальнейшие ваши действия будут парализованы. Такой трюк может привести к нарушению целостности данных, поэтому перед большим количеством изменений лучше все же заблокировать всю таблицу.

Информации о сессиях

Для блокировки всей таблицы лучше использовать не SELECT FOR UPDATE, а LOCK TABLE IN EXCLUSIVE MODE. Этот оператор блокирует всю таблицу сразу, а не каждую строку в отдельности. Это намного эффективнее, когда воздействию подвергается вся таблица или большая ее часть. Заблокировав всю таблицу, вы можете не торопясь модифицировать данные и никто другой не сможет вас прервать от этого интересного занятия.

Итого Блокировки – очень мощное и удобное средство для многопользовательских приложений. Используйте их и вы избавитесь от множества проблем, а приобретете минимальные проблемы, которые легко разрешимы. Главное следовать следующим правилам: 1. Старайтесь блокировать минимально необходимое количество записей в таблице. 2. Не забудьте по закрытию формы сохранить или откатить изменения и закрыть набор данных, чтобы освободить ресурс. Блокировки гарантируют, что введенные пользователем данные будут сохранены в базе, и никто другой в этот момент не сможет их изменить. Другой пользователь получит доступ к документу только после его освобождения, а значит, не сможет затереть изменения.


А теперь серьезный недостаток – если на какую-то запись в таблице есть блокировка, то у вас возникнут проблемы с изменением структуры. Это значит, что нельзя будет добавить или удалить какое-то поле. Чтобы внести изменения в структуру таблицы, придется ждать окончания рабочего дня или просить всех пользователей выйти из программы. В этом случае, если в системе останутся какие-то не закрытые сессии, то их можно будет убивать, потому что они явно мертвые. Листинг 1: procedure TSomeDocument.FormShow(Sender: TObject); var oldSql : String; begin // сохраняем запрос и добавляем операторы блокировки oldSql:=odsDocs.SQL.Text; odsDocs.SQL.Add(' FOR UPDATE NOWAIT'); try // пытаемся открыть набор данных odsDocs.open; odsDocs.ReadOnly:=false; // проверяем, найден ли документ if odsDocs.RecordCount=0 then begin Showmessage('Документ не найден, пока вы думали, его уже удалили'); Close; exit; end; except // документ заблокирован, поэтому открываем его только для чтения odsDocs.SQL.Text:=oldSql; Showmessage('Документ заблокирован другим пользователем, открываем только для чтения'); odsDocs.open; odsDocs.ReadOnly:=true; end; end;

By, Фленов Михаил aka Horrific E-mail: horrific@vr-online.ru WWW: www.vr-online.ru


Безопасность баз данных предприятия Не секрет, что, в наше время миром правит информация. Если предприятие дорожит своей интеллектуальной собственностью, если каждый работник может легко получить необходимую (и не более того) информацию, то предприятие может надеться на рост. Если данные находятся в хаосе… несмотря на энтузиазм сотрудников, в большинстве случаев предприятие ожидает крах. Сколько раз мы слышали, о том, что база данных какого-то предприятия или государственной структуры появилось на прилавках магазина. Пятнадцать минут назад я ехал в метро на работу, и мне в открытую предлагали купить последние базы данных телефонов любых операторов, налоговые базы данных и кучу другой полезной и бесполезной информации. Это говорит о том, что компании, которые должны следить за безопасностью своих данных относятся к своему делу несерьезно или просто наплевательски. Сегодня мы рассмотрим основы безопасности баз данных и немного поговорим о примерах защиты информации в Oracle (правда, теоретические основы защиты информации в БД, которые мы рассмотрим в этой статье, будут полезны и людям, работающим с другими базами данных).

Основа доступа В большинстве организаций, где я работал или с которыми мне приходилось сталкиваться по работе, все администраторы и программисты имели полный доступ к базам данных, а любой сотрудник отдела ИТ являлся богом в сети и мог делать все, что угодно. Почему так происходит? На это есть две причины: 1. Работая в одном отделе или департаменте, сотрудники видят друг друга каждый день по 8 часов, завязываются дружеские отношения. Как же не дать другу полный доступ? Дружба дружбой, а работа есть работа. 2. Распределение некоторых прав доступа и изменения какой-то конфигурации может потребовать определенных привилегий. Администраторы иногда ленятся или просто считают, что программисты выполнят настройку лучше, поэтому дают программистам излишние права доступа. Никогда программисты не должны заниматься администрированием и для этого у них не должно быть прав. На моем опыте вторая проблема встречается очень часто, поэтому мы рассмотрим ее более подробно. Разрабатывая программы для баз данных, программисты знают пароль супер пользователя или просто имеют права администратора базы данных. Это излишне и абсолютно небезопасно. Полные права должен иметь только администратор базы и больше никто. Даже начальник отдела, директор и лучшие друзья могут обойтись меньшими привилегиями. Например, для разработки программ достаточно иметь права владельца схемы в базе данных Oracle, где находятся рабочие таблицы. Этого достаточно для создания новых таблиц, пакетов, индексов и любых других объектов, а также, для распределения прав доступа к объектам схемы другим пользователям. Права системы для полноценной работы абсолютно не нужны. Если в вашем штате нет администратора баз данных, то это очень плохо. Лучше, когда за базу данных, ее производительность, оптимизацию и безопасность отвечает отдельный сотрудник. В крайнем случае, необходимо выделить одного программиста, который будет ответственным, и только он будет иметь исключительные права.


Судя по статистике, потери данных чаще всего происходят изнутри компании, т.е. ее сотрудниками. Странно, но не смотря на это, большинство продолжают игнорировать эту угрозу, а различные базы данных продолжают появляться на дисках в свободном доступе даже в метрополитене.

Пользователи и роли Для распределения прав доступа в базах данных используется два типа объектов, которым можно распределять права – пользователи и роли. Роли – это как группы, которые объединяют пользователей, которые должны иметь схожие права. Например, всем бухгалтерам может потребоваться доступ к финансовым документам. Чтобы каждому бухгалтеру в отдельности не выдавать права, их можно объединить в роль, а потом этой роли дать необходимый доступ. Как видите, принцип работы ролей схож с группами в ОС Windows. Схож, но имеет и отличия. Недаром, в базе данных могут быть реализованы все три типа объектов – пользователи, группы и роли, но в большинстве баз реализуют только два – первый и последний. Всеми этими объектами необходимо управлять и отслеживать, чтобы каждый пользователь, наследуя права, предоставляемые ролями, имел доступ только к тем данным, которые действительно необходимы для решения поставленных задач. К правам доступа нужно относиться как к правилам сетевого экрана, потому что с их помощью можно решить одну и ту же задачу – позволить пользователю выполнять только определенные действия и предотвратить возможную потерю или разрушение. С помощью ролей, достаточно удобно управлять доступом, выделять разрешения или запрет целой группе пользователей. Одна роль может входить в другую, то есть наследовать ее права доступа. Таким образом, можно создать иерархическую структуру прав. Все сотрудники, которые должны иметь доступ к корпоративной базе данных могут быть включены в одну роль, которой будут предоставлены минимальные права. Теперь, если необходимы дополнительные привилегии, доступ к определенным таблицам, то для этого можно добавлять пользователя в другие роли (если такой же доступ нужен группе) или выделять права конкретной учетной записи. В программе, для контроля доступа к таблицам, можно после каждой попытки открытия набора данных проверять результат на наличие ошибки. Если произошла ошибка доступа, то пользователю запрещено обращаться к указанной таблице. Таким образом, нет необходимости реализовывать права доступа к клиентском приложении. Но если вы захотите это реализовать, хуже от этого не будет. По крайней мере я, ничего критического в этом не вижу, а даже наоборот.

Аудит Если в вашей базе данных каждый пользователь работает под своей учетной записью, то для определения текущего пользователя можно обратиться к переменной user, например, так: SELECT user from dual Этот запрос вернет имя пользователя, под которым открыто соединение с сервером. Если под одним именем может войти несколько человек, то этот запрос уже для обоих вернет одно и тоже имя и для аудита такое положение дел ничего не даст. Особенно, если управление блокировками будет происходить на уровне сервера.


Если под одним именем может войти несколько человек, то идентифицировать, кто именно вощел все еще можно, но уже сложнее. Следующий запрос, позволяет получить более детальную информацию о сессии: select s.user#, s.username, s.osuser, s.terminal, s.program from sys.v_$session s WHERE username=user

Запрос вернул имя пользователя, подключенного к базе, имя в ОС, имя терминала (компьютера) и программу

Здесь происходит обращение к представлению v_$session из системной схемы sys. Это представление возвращает информацию о соединениях с сервером. В секции WHERE мы ограничиваем вывод только текущим пользователем. В качестве результата запрос вернет идентификатор пользователя, имя, имя в ОС, терминал и программу, из которой создано подключение. Теперь, зная имя и компьютер, пользователь идентифицируется однозначно. Чтобы узнать, в какие роли входит текущий пользователь, можно выполнить следующий запрос: SELECT GRANTED_ROLE FROM dba_role_privs WHERE grantee=user

Безопасность учетных записей Нет, мы не будем говорить о том, что не должно быть паролей по умолчанию. Некоторые базы данных (например, MySQL) грешат тем, что после установки устанавливают простой и общеизвестный системный пароль. Мы также не будем говорить о том, что пароль должен быть сложным. Это правило относиться ко всем ИТ областям и должно быть известно даже начинающему пользователю. Мы поговорим о том, что касается именно баз данных.

Как показывает мой опыт, очень часто при разработке корпоративных программ, программисты используют одну учетную запись для доступа к базе данных. Параметры этой учетной записи прописываются прямо в программе, а доступ к определенным модулям (бухгалтерия, склад, отдел кадров и т.д.) реализовывается программно. С одной стороны, этот способ удобен,


потому что программа может подключаться к базе с администраторскими правами и не придется мучаться с ролями и распределением прав на таблицы. А с другой стороны, этот способ далеко не безопасен. Уровень пользователей в нашей стране растет, да и знакомые у сотрудников вашей компании могут быть достаточно просвещенными в сфере безопасности и взлома. Узнав имя и пароль, под которыми программа соединяется с базой, простой пользователь сможет получить больше возможностей, чем вы этого хотели. Язык SQL не является секретом и слишком уж сложным языком. Существует множество программ, с помощью которых, можно легко просмотреть любые данные в базе. Зная имя и пароль, кто угодно может украсть всю информацию и через пару дней она будет в любом ларьке, торгующем дисками. Имя пользователя и пароль никогда не должны сохраняться в программе. Доступ в саму программу также должен быть ограниченным и невозможным для стороннего человека. Вполне логично будет объединить обе авторизации в одну. Каждому пользователю в организации необходимо завести отдельную учетную запись на сервере баз данных с необходимым минимумом прав и именно эти имя/пароль должны использоваться при авторизации в программе. Можно использовать распространенную и очень эффективную логику - если с помощью введенных данных программа смогла авторизоваться в базе данных, то доступ разрешен, если нет, то необходимо прервать выполнение программы. Просто и эффективно, потому что мы задействовали авторизацию базы данных. Чтобы проконтролировать, что под одним именем одновременно не работают с двух разных компьютеров, можно выполнить следующий запрос: select s.username, s.terminal, count(*) from sys.v_$session s WHERE username=user HAVING count(*)>1 GROUP BY s.username, s.terminal В идеале, он не должен вернуть ни одной записи.

Представления Представления или View являются очень удобным способом абстрагироваться от структуры данных и в то же время построить дополнительный уровень защиты. Да, представления отрицательно сказываются на производительности, но положительно сказываются на безопасности. На них можно выдавать собственные права доступа, при этом, сами таблицы, из которых выбираются данные, могут оставаться недоступными для данной учетной записи. Когда лучше использовать представления, а когда нет? Для начала определимся, в чем кроется недостаток. Представление – это запрос SELECT на выборку данных. Он может обращаться как к одной, так и к нескольким таблицам. Если просто выбрать данные из представления, то падения производительности не будет. Но мы можем использовать представления в других запросах на выборку данных, и обращаться к ним, как к таблицам. Например: SELECT список полей FROM представление, таблица1, таблица2 WHERE какие-то параметры


В данном случае, запрос выбирает данные из представления и двух таблиц. При этом, наводиться связь между всеми объектами и может быть установлено дополнительное условие. А теперь посмотрим на этот запрос так, как видит его оптимизатор базы данных: SELECT список полей FROM (SELECT ...), таблица1, таблица2 WHERE какие-то параметры В данном случае, я заменил представление на абстрактный запрос SELECT (он указан в скобках) из которого и состоит view. Получается, что сервер видит запрос с подзапросом. Если подзапрос возвращает динамические данные, а не статическое значение, то такая выборка данных будет работать медленнее, чем если бы мы написали то же самое, но избавились бы от подзапроса. Если у вас нет глубоких вложения запросов (запрос-представление1-представление2...), то можно смело использовать view, без опаски потерять слишком много тактов в производительности. Потери минимальны, зато вы получаете лишний уровень безопасности, на который можно распределять права доступа.

Безопасность данных Никогда не стоит выдавать полный доступ на объекты без особой необходимости. Чтобы не выдать лишнего, я всегда начинаю распределение прав с разрешения только выборки данных, т.е. выполнения запроса SELECT. Если пользователю действительно необходима вставка новых записей и он не может выполнять поставленные перед ним задачи, то в этом случае добавляем разрешение INSERT на определенную таблицу. Для данных наиболее опасными являются операции изменения и удаления, т.е. UPDATE и DELETE соответственно. К распределению этих прав нужно подходить еще более тщательно. Убедитесь, что данные действительно могут изменяться или удаляться и только в этом случае выделяйте соответствующие права. Некоторые таблицы по своей сути должны только пополняться. Необходимо также убедиться, что данные будут подвергаться воздействию достаточно часто. Например, таблица сотрудников организации может пополняться и изменяться, но никогда и ни одна запись не должна удаляться. Удаление может повлиять на историю работы сотрудников в организации, на отчетность и целостность данных. Да, возможен вариант, когда сотрудник отдела кадров случайно завел лишнюю запись и хочет ее удалить, но такие случаи происходят редко и исправление ошибки можно возложить на администратора базы данных. Мы понимаем, что никому не хочется выполнять лишнюю работу и проще выдать разрешение, но безопасность дороже. Предоставление прав происходит с помощью оператора GRANT. В общем виде он выглядит следующим образом: GRANT на что давать права ON объекты TO пользователи или роли Например, следующий запрос предоставляет право на вставку и просмотр таблицы TestTable пользователю User1: GRANT Select, Insert ON TestTable TO User1 Какие роли уже выданы пользователю, можно легко увидеть с помощью SQL Navigator. В версии 4.4 для этого в дереве объектов выбираем раздел Users/Имя пользователя. Здесь вы увидите раздел Object Privileges, в котором и находятся все разрешенные пользователю действия.


Ключи Многие бояться внешних ключей, потому что их наличие очень часто запрещает удаление данных, когда есть внешнее соединение. Проблема легко решается, если установить каскадное удаление, но большинство специалистов резко отрицательно относятся к этой возможности. Одно нелепое действие может уничтожить очень важные данные, без каких либо запросов на подтверждение. Связанные данные должны удаляться явно, и только если пользователь осознано подтвердил, что данные могут удаляться.

Простая утилита, для выполнения SQL запросов на сервере

Не стоит бояться внешних ключей. Они предоставляют нам удобный способ контроля целостности данных со стороны сервера баз данных, т.е. это то же безопасность, а безопасность лишней не бывает. То, что могут возникнуть проблемы с удалением, то это только плюс. Лучше лишний раз не удалить данные, чем потерять их на веки. Внешний ключ, если он используется совместно с индексом, практически не уменьшает производительности. Это всего лишь небольшая проверка во время удаления данных или изменения ключевого поля, по которому происходит связь данных в разных таблицах.

Резервное копирование Резервное копирование тоже один из факторов защиты, которым мы пренебрегаем до первой потери данных, но я бы отнес его к основным. Потеря данных может быть не только от хакеров и неумелых действий пользователей, но и из-за неисправностей в оборудовании. Сбои в оборудовании могут привести к потери базы данных, на восстановление которой могут уйти часы, а может даже и дни. Необходимо заранее разработать наиболее эффективную политику резервного копирования. Что под этим понимается? Простои в работе, после потери данных и до момента восстановления работоспособности должны быть минимальны. Минимальны должны быть и потери данных, а само резервирование не должно мешать работе пользователей. Если позволяют финансы и возможности, то лучше использовать такие системы как RAID, кластер или даже репликацию данных.


очень удобная и наглядная среда работы с запросами и управление базой данных Oracle

Резервное копирование и восстановление – это отдельная и очень интересная тема. Не затронуть ее здесь мы не могли, но и рассматривать полностью не будем.

Итого Мы рассмотрели основы безопасности, с небольшим уклоном в сторону Oracle. Это только начало и в будущем мы вернемся к этой теме, чтобы рассмотреть ее глубже. Но не стоит забывать, что средства защиты, предоставляемые базой данных – это только один уровень, а защита должна быть многоуровневой. Чтобы хоть немного спать спокойно, необходимо реализовать в сети, серверах и на всех компьютерах весь комплекс безопасности, а именно – антивирусы, антишпионы, сетевые экраны VPN, IDS и т.д. Чем больше уровней защиты, тем сложнее будет их преодолеть. Должна быть четкая политика безопасности и контроль. Если какой-либо сотрудник увольняется, то его учетная запись удаляется. Если же у вас пользователи работают под одним паролем или для выполнения каких либо действий используют групповой пароль, то все эти пароли должны измениться. Например, если увольняется администратор, а у вас все администраторы используют одну учетную запись, то обязательно поменяйте пароль. Безопасность необходима и не только от хакеров, но и от "начинающих" пользователей, которые могут разрушить данные по своей неопытности. Хорошая политика безопасности позволяет предотвратить и такие случаи и такую возможность нельзя исключать. Лучше заранее предусмотреть возможность обезопасить данные от неопытности, чем потом потерять уйму времени на восстановление данных и получить излишние простои в работе.

By, Фленов Михаил aka Horrific E-mail: horrific@vr-online.ru WWW: www.vr-online.ru


Графика

От редактора Хотя наш, сайт по большей части ориентирован на программистов, тема «графика» многим интересна. Если ты из их числа, то сегодня тебя ждет небольшая статья от нашего читателя. Он доходчиво расскажет, как сделать свой баннер.


Баннер своими руками Каждый чел, кто когда-нибудь делал свой сайт, сталкивался с такой проблемой, как изготовление баннера. В своё время я тоже столкнулся с этой проблемой, но нашлись люди и показали, как это делается. Вот и я хочу показать вам, как можно сделать баннер своими руками за 10 минут. Итак, начнём. Что нам понадобится! Во-первих: проги Gif Studio Pro(эта программа есть на диске, приложенной к книге “Web-сервер глазами хакерами”) и Adobe Photoshop(я использовал версию CS2). Вместо проги Gif Studio Pro вы можете использовать программу Ulead Gif Animator. Так, во-вторих: умение рисовать в фотожопе aka Photoshop. 1 стадия разработки, рисуем сам баннер в PHOTOSHOPe. Загружаем Photoshop и создаём новый файл(Ctr+N). Значения ширины и высоты ставим 88х31 и жмем OK.

Далее рисуете какие-нибудь рисунки и сохраняете их.


Вторая стадия разработки, изготовление самого баннера в Gif Studio Pro. Запускаем прогу Gif Studio Pro и выбираем пункт Create new project. Далее в появившемся меню выбираем пункт Use custom size и устанавливаем значения 88х31. Жмем Next и в следующем окне выбираем пункт Center in the frame. Далее в следующем окне, где написано “How long would you like each frame to be displayed?”, устанавливаем значение 3000. Следующие: выбираем свой рисунки, только смотрите не перепутайте правильность расстановок рисунок, а то получиться совсем не то, что вы хотите. Жмем Finish и всё. Потом сохраняем в формате Gif и всё.


В заключение В заключение хочется вам сказать, что это моя первая статья, так что не судите строго. Может быть, эта статья вам покажется слишком нудной и не интересной, но для начинающих эта статья будет очень полезной. Всем PEACE!!!

By, ~][ero_of_Keybo@rd~ aka MrBoa E-mail: no_email


Без рамки

От редактора Ты умудрился дочитать/пролистать журнал до этой странице? Раз так, то тебе просто необходимо просто отдохнуть и почитать очередной рассказ от Neon_Kaligula. После знакомства с рассказом, удели внимание статье про СОРМ. Данная тема тебе должна быть интересна ;-).


Вирт

Мартынов Алексей aka Neon_Kaligula - 25.07.2007 ==================================

- Ты псих вообще! - улыбаясь, говорила она мне. - Вуайерист, так это вроде называется. - Почему? - Тут люди... я не могу так... в культурном месте. - Это станция метро, причём пустая. Где ты тут людей увидела? К тому же культурных. - Тебе это нравится просто, когда у всех на глазах. - Не, - я не отпускал её, - вуайеристы получают удовольствие от того, что на них смотрят. А мне как-то пофиг кто там на меня смотрит, я от тебя кайфую, а не от них. ... Наступило утро какого-то дня. Я уже сбился со счёту. "Приезжай сейчас, жду тебя" - гласила СМСка. Она действительно ждала. Впервые в жизни она приехала не то, чтобы вовремя, а даже раньше меня, совсем не опоздав. А я впервые почувствовал, что заставлял её ждать. Впрочем, она не выказывала недовольства, а скорее наоборот. - Привет. - Привет, - её высокий слегка мурлыкающий голос я каждый раз забывал. Её игровой образ хоть и был похож на неё саму не только по духу и в какой-то степени по внешности, но каждый раз, когда я слышал её голос после разлуки, он мне казался чем-то новым. - Ты никогда не плясал с дьяволами при лунном свете? - Что? - Это цитата. (надеюсь, я правильно её вспомнила) Что-то сегодня должно произойти. Мы шли по каким-то развалинам или недостройкам. Не знаю, как нас сюда занесло: сухая почва с врытыми местами бетонными плитами, растительность была скудна - иногда пробивались пучки слегка пожелтевшей травы, такой уродливой с колючками и закрученными листьями, как будто чей-то злой ум проводил над ней генетические эксперименты. Вокруг были какие-то брошенные одноэтажные строения, проржавевшие торговые палатки. Она вошла на детскую площадку и села на качели. Я стоял рядом. Мы познакомились с ней примерно полгода назад в виртуале. Не помню что это было - чат, форум, сайт знакомств или онлайн-игра. Хотя сайт знакомств отметаем, ибо я не искал её


специально, но нашёл, о чём не жалею. После знакомства мы успели побывать везде - и в чате и в форуме, но чаще всего виделись в онлайн-игре потому как это было больше всего похоже на реальную жизнь - тут было дело, а не только разговоры. - Ты наркоманка... Да и я вместе с тобой, - говорил я ей иногда, может всего пару раз. Она лишь улыбалась. - Ты мне что-то хотела сказать. Впрочем, последнего предложения я не сказал. Вернее, я хотел бы, чтобы это было так. Она никак не отреагировала на него, поэтому, скорее всего, я это только подумал. Я купил ей мороженое в палатке неподалёку. Она стала неспешно поедать его и слабо качаться на качелях. Она что-то говорила при этом, но я не вслушивался в слова. Говорила она о своих родных, о своей жизни в родном городе, о детских планах и надеждах. А потом она надолго замолчала. - Какая красивая луна, - сказала она тихо. Я поднял глаза. Несмотря на то, что ещё было светло, на краю неба уже висела луна. Месяц, если быть точным. Такой надкушенный с одного бока с ровными краями и бугристой поверхностью. Он почти слился с небосводом, будучи всего лишь чуть темнее. - За что ты меня любишь? - спросила она с лёгкой нервозностью в голосе. - За что?.. Нельзя любить за что-то. Можно любить, несмотря на что-то. А любовь за что-то это расчёт. - Но я же злая, я ругаюсь матом. Я же сволочь, в конце концов. За что вы меня любите? - Фигассе. Ну что можно было ответить на такое. Только то, что она стала для меня тем единственным человечком, ради которого стоит жить и делать что-то в этой жизни. Судя по всему, это было обоюдным. Но её манера самокритики и самоуничижения иногда убивала меня. - Мне пора, - сказала она, вставая. - До встречи, - сказал я. Она ушла. Не помню, что происходило дальше. "Да у неё же болезней полный мешок и сложный характер. К тому же она тебя старше. Нахрена она тебе сплющилась?" - Тебе-то какая разница? - Привет. - Привет, - я уже не смотрел ей в лицо, я просто знал, что это она. Тот же голос, те же повадки, только образ другой. - Смотри, какая красивая луна, - сказала она и улыбнулась. Я взглянул на луну. На другой стороне небосвода висела вторая луна. Точно такая же, как и первая, к тому же они были симметричны в своём положении на небе. Я даже знал причину почему их две.


- Где луна? - спросил я. - Да вот же, - она указала на вторую луну. - Ты не видишь? - Теперь вижу, - она не видела двух лун. По странному стечению обстоятельств их видел только я. Одна из этих лун была настоящей, а другая - тенью. Это была она и не она одновременно. Это была проекция реальной девушки в виртуальный мир. Но она тут пользовалась теми же правами, что и в реальном. Характер был тот же самый. "Она же лентяйка. К тому же подсажена на виртуальное общение. Из образования только техникум, в институт не устаивается, работать не хочет. Зачем она тебе такая?" - Тебе-то что? - Пойдём? - спросила она. - Пойдём, - отвечал я. Начинался новый цикл. Мы уже не были в реальном мире, всё вокруг было виртуально, только я всё время упускаю момент перехода. Сейчас мы вместе просидим несколько часов в этой созданной кем-то реальности. Мне это не нравится, я уже давно не вижу тут жизни, только смерть. Но я хочу быть с ней любым способом. ... - У меня нету стимулов жить в реале. Что там хорошего? Там люди. Злые и жестокие. И нет стимулов. Ты - это не стимул, ты - это совсем другое. Я пробовала занять себя, работать, но каждый день я сидела с мыслью "когда же всё это закончится"... Я так не могу. Поэтому убежала. - Я понимаю. Но хочу попробовать. ... Я стоял перед дверью в собственную квартиру, ища правой рукой в сумке ключ. В эти несколько секунд поиска ключа мои мысли обычно витают где-то вдалеке, я слабо реагирую на окружающую действительность. Кто-то постучался, и по полу зашуршала бумага. Я оглянулся. Кто здесь мог стучаться? Ведь я пока ещё не вошёл. Но посреди огромного тронного зала лежал конверт. Я открыл его: внутри была её фотография ещё в десятом классе. Забавная такая - накрасили так, что выглядит много старше своего возраста. Это что, опять виртуал? И снова я пропустил момент перехода в него? С недавних пор мне стало казаться, что оба этих мира - реал и виртуал - оба настоящие... Надо просто выбрать, где ты хочешь жить. "А вот у неё... А вот она... Да ты же себя с ней погубишь. Зачем она тебе?" - Тебе-то что? Какая вам всем разница, зачем она мне?! Просто я люблю её.


СОРМ на службе государства Любое государство должно иметь возможность обеспечивать соблюдение своих законов. Для этого в государствах создаются органы, которые обязаны контролировать соблюдения законодательства. С появлением мобильной связи и широкой распространенности сети Интернет осуществлять контроль стало труднее. Современные преступники это понимают, поэтому Интернет преступления растут. Чтобы как-то противостоять современной преступности, государство начинает вносить поправки в свою законодательную базу, которая позволит спецслужбам получать доступ к необходимой информации.

История Спецслужбы нашей страны при особой необходимости всегда могли получить доступ к телефонным переговорам граждан, организовывая прослушку определенных телефонных номеров. Все наши АТС, были оснащены возможностью прослушивания. Используя возможность так называемой прослушки, нашей доблестной милиции и другим компетентным органам удавалось раскрыть множество преступлений. Возможность организация прослушки была закреплена в законе «Об оперативно-разыскной деятельности» от 12.08.1995. В этом законе приведен список оперативно-разыскных мероприятий. Среди таких мероприятий: прослушивание телефонных переговоров, контроль почтовых отправлений, снятие информации с технических каналов связи и т.д. Полный перечень можно посмотреть в статье 6 закона. Шло время, в России предоставляемый круг телекоммуникационных услуг стал существенно расширяться. Мобильная связь стала стремительно окутывать все уголки нашей необъятной Родины, Интернет с каждым годом стал все более популярным, появляется такая новомодная услуга как VoIP. При возникающей необходимости спецслужбам становится трудно (а зачастую и невозможно) получить доступ к необходимой информации. В результате этого пришлось обновлять законодательную базу. С 1998 года, в выдаваемые лицензии на предоставление услуг связи Минсвязи России включило новое условие. Согласно которому, лицензиат (как пример, будущий провайдер, оператор сотовой связи и т.д.) при разработке, создании и эксплуатации сети связи обязан оказывать содействие и предоставлять органам, осуществляющим оперативно-разыскную деятельность возможность проведения оперативно-разыскных мероприятий на сети связи. Вроде все пошло хорошо, каждый получал, что хотел – провайдер мог начинать осуществлять свою деятельность, уполномоченные органы в случае необходимости могли получить возможность, просматривать необходимую информацию, вот только дальше самой возможности получить доступ к информации фактически не могло. Вся проблема заключалась в том, что проведение таких мероприятий возможно только при условии, что у оператора/провайдера будет установлено дополнительное оборудование. Вследствие этого нужно было в кратчайшие сроки разработать соответствующие оборудование и весь необходимый комплекс программ, иначе говоря, требовалось создать систему технических средств по обеспечению функций оперативно-разысных мероприятий – СОРМ. Как обычно, всех нюансов за один раз в нашей стране учти не смогли. В результате этого, когда уже была реальная возможность внедрения СОРМ провайдерам/оператором начались проблемы с законодательством. Нигде не было сказано, что провайдер обязан установить у себя дополнительное оборудование. В общем, мероприятие по внедрению СОРМ пришлось опять отложить и заняться законодательной базой.


Тем временем журналисты подняли крик во все голоса. В прессе, в Интернет стали появляться «скандальные» статьи о СОРМ и о его последствиях для всех пользователей и абонентов. Высказывались самые фантастические предположения, о том, что, якобы все будут под контролем большого брата (ФСБ), что по сути СОРМ это чуть ли не аналог американской шпионской сети Эшелон. В газетах, журналах стали появляться статьи с заголовками «Под колпаком у ФСБ», в Интернете как грибы стали появляться форумы с обсуждением проблемы СОРМ (часть этих форумов до сих пор существует). Народ получил интересную тему, все любители теории заговора получили превосходную тему для размышлений. 25 июля 2000 года был подписан приказ Минсвязи № 130 «О порадке внедрения системы технических средств по обеспечению оперативно-разыскных мероприятий на сетях телефонной, подвижной и беспроводной связи и персонального радиовызова общего пользования». Концепция системы серьезно обновилась.

СОРМ завоевывает Россию СОРМ признана обеспечить возможность слежения за определенным пользователем сети независимо от провайдера и типа подключения. Предназначение системы – борьба с всевозможной преступностью. Если поискать информацию о СОРМ в сети Интернет, то можно встретить рассказы про разные версии системы. Журналисты рассказывают про возможности СОРМ второй и третьей версии. На самом деле эти данные нельзя использовать как достоверные. Все принятые нормативные акты, относящиеся к СОРМ, не разграничивают ее на версии. Множество всяких версий СОРМ – это бред многих журналистов, направленный для накала «страстей». С развитием информационных технологий, СОРМ приобретает дополнительные функции. Например, изначально СОРМ предназначался для прослушки телефонных разговоров. С массовым распространением сети Интернет, в СОРМ добавили возможность анализа передаваемых/получаемых пользователем данных. Именно эту «возможность» журналисты окрестили как рождение СОРМ-2.

Принцип внедрения СОРМ Проблема первой версии СОРМ была – отсутствие необходимых законов. К становлению второй версии этот пробел был учтен. Теперь, следуя закону, процесс внедрения СОРМ можно разделить на несколько стадий: 1. Подготовительная. На этой стадии операторы связи должны составить план мероприятий необходимых для внедрения СОРМ. В такой план входят следующие действия: • согласование с представителем территориальных органов ФСБ России спецификации на оборудование СОРМ. • заключение договора на покупку оборудования СОРМ • проведение работ по запуску оборудования СОРМ в действие. • проведение испытаний оборудования. • начало опытной эксплуатации оборудования СОРМ, устранение недостатков, выявленных органами ФСБ. • долгожданное завершение – сдача СОРМ в эксплуатацию. После составление подобного плана, оператору/провайдеру связи нужно пройти процесс согласования с представителями органов ФСБ. После получения одобрения, план отправляется в управление по надзору за связью и информатизации, на окончательное рассмотрение.


2. Покупка оборудования. На этой стадии, как видно из названия оператор связи приобретает оборудование. После этого начинается процесс ввода в эксплуатацию. Во время этого процесса проводятся все необходимые работы по запуску, тестирования оборудования. 3. Запуск. Стадию запуска можно разделить на две подстадии. Первая – предварительное тестирование. На этом этапе все необходимое оборудование должно быть установлено и настроено. Проводится тщательное тестирование с целью выявления всех каких-либо проблем и только потом наступает последний этап – окончательный запуск системы.

Техническая сторона Система СОРМ включает в себя три компонента: • аппаратно-программная часть. (Устанавливается у оператора связи) • удаленный пункт управления. (Устанавливается у правоохранительных органов) • канал (ы) передачи данных (Обеспечивается провайдером, для установки связи с пунктом удаленного управления). Если в качестве примера рассматривать Интернет-провайдера, то работа системы выглядит следующем образом. У провайдера устанавливается специальное устройство. Это устройство подключается непосредственно к Интернет-каналу, а оборудование провайдера для организации доступа в инет подключается уже оборудованию СОРМ. В результате получается, что весь входящий и исходящий трафик будет проходить через спец устройство, а значит, в случае необходимости сможет быть перехвачен правоохранительными органами. СОРМ обеспечивает два режима передачи информации: • •

передача статистической информации передача полной информации.

При работе в первом режиме на удаленный пункт управления должна передаваться информация о времени начала/завершения сеанса связи, сетевые адреса (имена) пользователей. Режим передачи полной информации отличается тем, что помимо перечисленных сведений должна передаваться информация, которую принимает или отправляет пользователь. СОРМ обладает надежной системы несанкционированным путем невозможно.

защиты.

Получение

каких-либо

данных

Каких-либо более детальных и самое главное официальных данных по данному вопросу нет. Все самое интересное находится под грифом «Совершенно секретно». Кое-какую информацию можно получить из «Технических требований к системе технических средств по обеспечению функций оперативно-розыскных мероприятий на электронных АТС».

Отрицательная сторона СОРМ Читая, выше приведенную информацию многие могут сказать, что СОРМ действительно нужно усиленно внедрять, другие наоборот возмутятся, ведь, по сути, в нашу личную жизнь начинают вторгаться люди в погонах. Все что мы делаем в сети, может быть доступно третьим лицам. В этом и заключается отрицательная сторона вопроса. Но не так страшен черт, как его малюют. Если посмотреть на другие государства, то у них подобные системы уже давно используются и граждане спокойно к этому относятся. Если рассматривать вопрос личной безопасности нахождения в сети, то лучше всего обратить внимание на деятельность компьютерных преступников – крэкеров. Разве их стало меньше?


Скорее наоборот, их ряды растут с каждым днем и они не боятся новых трюков от государства с целью их поимки и разоблачения. Сесть Интернет устроена так, что полностью взять ее под контроль невозможно. Всегда можно найти какой-то способ обхождения правил. Например, чтобы не попасться под действие СОРМ или никакой другой системы можно использовать шифрование. От СОРМ можно «скрыться». Используя стойкие алгоритмы шифрования, можно защититься на 99% от всевозможных проблем. В таких случаях, даже если информация и будет перехвачена спецслужбами, на ее расшифровку им понадобится много времени. Еще одной немаловажной отрицательной стороной является возможность подкупа соответствующих сотрудников. В России, взяточничество – одна из самых главных проблем. Одно дело когда взятку берез обычный сотрудник милиции и отпускает на свободу преступника, другое если взятку возьмет человек имеющий отношений к СОРМ. Ведь он сможет перехватить любую (почти любую) информацию, которая будет нужна третьим лицам. Можно только представить себе, как таким образом можно будет разорять компании. Достаточно поставить под наблюдение всю сетевую активность компании и выделить немного времени на ожидание. В заключении, я хотел бы поблагодарить агента Andrew за предоставленную информацию о СОРМ, которая впоследствии помогла написать данную статью.

Полезные ссылки: http://www.libertarium.ru – очень полезный сайт. Здесь можно почитать кучу разных статей/мнений относительно внедрения СОРМ. На сайте также можно найти интервью, взятые у сотрудников ФСБ. http://www.electrosvyaz.com/ - на этом сайте находится отличный форум, посвященный электросвязи. Стоит только запустить поиск по ключевому слову СОРМ и перед вашими глазами предстанут множество тем обсуждения данного вопроса. Среди участников форума достаточно много компетентных людей в вопросах связанных с СОРМ. http://rg.ru – официальный сайт российской газеты. Здесь можно почитать все нормативные акты/законы касающиеся темы СОРМ.

Вопросы для экспертов: 1. Есть ли перспектива у этих систем (Эшелон, СОРМ)? 2. Какие аналоги у этих систем есть? 3. Как обстоят дела по внедрению СОРМ в России? 4. Каким образом внедряется СОРМ провайдерам? Кто будет нести все расходы? 5. Вообще как происходит процесс внедрения. Какое дополнительно оборудование нужно? 6. СОРМ в нашей стране реально работает? Были ли случаи, когда при нашим органам реальна помогла эта система? 7. СОРМ - 2, какие кардинальные отличия от первой версии. 8. Выгода использования СОРМ. Кому это выгодно? Непосредственно чиновникам или же для граждан тоже есть польза? 9. СОРМ и конституция. Как Вы считаете, не сильно ли нарушает концепция СОРМ конституционные права человека? Ведь по сути дела, все становятся под постоянным контролем. 10. Как вы считаете, тотальное внедрение системы СОРМ не сможет породить множество "нечестных" сотрудников со стороны наших спецслужб? Ведь, по сути, будет много желающих, которые захотят купить приватную информацию. А в нашем гос-ве,


платят не очень хорошие зарплаты. Т.е. соблазн продаться велик. 11. Сейчас в интернете можно найти высказывания о якобы новой системе СОРМ - 3. Что это? Гон журналистов и любителей теории заговора? :) 12. Каков статус полученной с помощью СОРМ информации, каковы доказательства ее подлинности, может ли она быть доказательством совершенного или готовящегося преступления? 13. Выгодно ли использование СОРМ с экономической точки зрения? Каковы затраты на содержание системы? 14. Есть ли в планах использования альтернативных разработок? Может, есть варианты дешевле? Или уже были попытки внедрения? 15. Возможности аппаратной и иной защиты от несанкционированного использования СОРМ? Хорошо ли защищена данная система?

Зараза Security-эксперт 1. Во многих странах есть законы, на основании которых могут быть прослушаны, например, телефонные переговоры. 2. В общем-то, СОРМ в первую очередь предназначался для операторов голосовой связи. Практически все операторы мобильной связи и большая часть операторов фиксированной имеют оборудование СОРМ, эти функции часто поддерживаются современными телефонными станциями или устанавливается дополнительное оборудование, например разработанное в ЛОНИИС. 4. Все расходы несет провайдер. В настоящее время достаточно большой процент провайдеров (менее 50% но точно более 10%) имеют имеют установленное оборудование СОРМ. 5. Обычно, внедрение сводится в установке специального оборудования. Например, комплекса СОРМович производства МЕРА (http://www.mera.ru/) или более дорогой "Омега". 6. Если говорить о телефонии - то да. Если об Интернет - то нет. Причины - отсутствие квалифицированных сотрудников в ФСБ, как минимум в регионах, нежелание заниматься делами связанными с интернет (большая часть дел ведется МВД, а конкуренция между этими двумя структурами уходит в довоенное прошлое вероятность что по делу, которое ведет МВБ будет задействован СОРМ-2 - минимальна), несоответствие возможностей оборудования реальной производительности каналов доступа. 7. СОРМ-2 действует уже много лет. Основные отличия - обязательность наличия оборудования для провайдеров. В общем-то СОРМ-2 действует не первый год, так чтопричин вспоминать СОРМ-1 нет. 8. Теоретически СОРМ нужен для того, чтобы иметь возможность применять законы. Кому это нужно? 9. Согласно части 2 стати 23 Конституции "Каждый имеет право на тайну переписки, телефонных переговоров, почтовых, телеграфных и иных сообщений. Ограничение этого права допускается только на основании судебного решения.". Т.е. осуществление СОРМ на основании судебного решения полностью соответствует конституции. 10. Думаю, что подобные инциденты наверняка будут. У желающего получить информацию будет 3 возможности - подкупить сотрудника ФСБ, подкупить сотрудника провайдера и установить оборудование для прослушки самостоятельно вместо двух.


11. Практически уверен, что никакого СОРМ-3 в ближайшее время не будет. В думе проблемами безопасности в сфере высоких технологий занимаются достаточно грамотные люди. Они вполне адекватно оценивают текущий уровень внедрения СОРМ-2. 12. Да, вполне может. 13. В общем-то затраты провайдера на поддержание системы минимальны, в основном только начальные затраты. Поэтому от внедрения данной системы страдают только небольшие провайдеры. 14. Не совсем корректный вопрос. Имеется ввиду какое-либо оборудование? Еще раз СОРМ это не проект. СОРМ это Следственно Оперативные Розыскные Мероприятия. Оборудование для СОРМ есть разное. Его стоимость примерно соответствует стоимости магистрального маршрутизатора. 15. По утверждению разработчиков - да, защищена. Данная система обязательно проходит аттестацию, поэтому явных пробелов в ее безопасности быть не должно. Не думаю, что защищенность этой системы существенно снижает общую защищенность сети провайдера.

Андрей Представитель небольшого регионального оператора связи, из числа тех кого Министерство связи и информационных технологий РФ своими реформами пытается выдавить с рынка. 1. Что значит "перспектива СОРМ"? СОРМ - система оперативно розыскных мероприятий, есть федеральный закон "Об оперативно розыскной деятельности", т.е. все узаконено и должно соблюдаться. Т.о. вопрос о "перспективе СОРМ" аналогичен вопросу "каковы перспективы соблюдения федеральных законов". Систему ЭШЕЛОН не видел. 2. Аналоги - это частные требования, предъявляемые к системам обеспечения СОРМ, УФСБ на местах. Это индивидуальные вопросы. 3. Полностью соблюсти ВСЕ законы в нашей стране практически невозможно. Тоже относится и к СОРМ. Поэтому СОРМ конечно внедряется, но не везде и не в полном объеме. 4. Процедуры внедрения СОРМ описаны в соотв. Постановлениях Правительства РФ. Относительно расходов на СОРМ, то, следуя букве закона, это расходное обязательство бюджета РФ, однако на практике я не знаю прецедентов получения операторам возмещения этих расходов из бюджета. 5. Оборудование, которое требуется, определяется планом внедрения СОРМ, который согласуют оператор и местное отделение УФСБ. В этом Плане описывается этапность и очередность внедрения. 6. Работает, но наверное не везде. В моей практике случаи реального применения СОРМ были. 7. Не видел официальных документов по СОРМ-2, которые являлись бы нормативноправовыми актами в соответствии с действующим законодательством. 8. Сложно сказать. Но и в ходе следствия по гражданским делам СОРМ применяется, так что можно сказать, что и гражданам польза есть. 9. Не нарушается. Той же Конституцией определены случаи, когда такие мероприятия могут проводиться.


10. Такой риск всегда существует, вне зависимости от уровня зарплаты. 12. Юридических тонкостей не знаю, но думаю, что является. 13. Выгодность - не тот термин, который тут надо применять. А выгодно платить налоги? :) Конечно нет, но законодательство нас обязывает это делать. Так что вопрос некорректен. Затраты могут быть очень разнообразны: ремонт вышедшей из строя аппаратуры, обучение, аренда каналов связи и т.п. 14. Зачастую дешевле внедрить не штатный СОРМ, а согласованный Планом с местным УФСБ. На деле чаще всего его и внедряют.

Антон Богатов Председатель совета директоров Инновационной компании "Некстер". Юрист, специализирующийся на вопросах правового обеспечения деятельности в области связи и информатизации. 1. Сложный вопрос. СОРМ использует прямой анализ сообщений. Использование этого метода возможно только в случаях, когда пользователь (отправитель сообщения) не использует криптографическую защиту. Фактически, данный метод применим только к добросовестным и законопослушным пользователям, либо к совсем уж беспечным злоумышленникам, поскольку криптографические программы общедоступны и широко распространены. 2. В той или иной степени системы поддержки ОРМ существуют во всех странах мира, причем очень давно. Первоначально эти системы предназначались для прослушки телефонных переговоров, впоследствии аналогичные системы вводились на сетях передачи данных. Внутреннее устройство систем поддержки ОРМ обычно конфиденциально или даже засекречено, однако цель СОРМ всегда одна и та же: обеспечение негласного оперативного контроля над информацией, передаваемой и хранящейся в сетях связи. 3. Вполне благополучно с формальной точки зрения. Ввод в эксплуатацию сетей связи допускается только после согласования плана мероприятий по обеспечению ОРМ с территориальным органом ФСБ РФ. На практике, разумеется, СОРМ подконтрольны ограниченное число потоков данных, просто в силу невозможности перлюстрации столь значительных объемов информации в реальном времени. Но по заданию органа, осуществляющего оперативно-разыскную деятельность (например, МВД), ФСБ имеет возможность "взять под колпак" любого пользователя на территории РФ. 4. СОРМ внедряется путем закупки весьма дорогостоящего оборудования (стоимость системы может достигать нескольких миллионов рублей) и его установки на узлах связи оператора связи. В соответствии с Федеральным законом "Об оперативно-розыскной деятельности" расходы должны осуществляться за счет федерального бюджета, однако на практике УФСБ находит способы убедить операторов создавать СОРМ за счет самих операторов связи. Таким образом, фактически СОРМ внедряется за счет абонентов и пользователей. 5. Оператор совместно с УФСБ разрабатывает план мероприятий по внедрению СОРМ. Эти планы весьма индивидуальны и определяются как структурой и размером сети оператора, так и структурой абонентской базы. План мероприятий имеет строго конфиденциальный характер. В России производится несколько типов оборудования СОРМ, однако цена их внедрения и качественные показатели примерно одинаковы.


Теоретически подразумевается, что весь трафик сети оператора должен проходить через СОРМ. Практически мощность СОРМ этого сделать не позволяет - в результате нужные каналы подключаются к системе по мере необходимости. 6. СОРМ в России - реальность. Применение СОРМ для решения оперативных задач тоже вполне реально. Однако, эти сведения разглашению не подлежат по понятным причинам. 7. СОРМ-2 предусматривает возможность удаленного дистанционного контроля за потоками информации из пункта управления в помещении УФСБ, причем без ведома оператора связи. При этом необходимо отметить, что Верховный суд РФ признал неправомерным использование СОРМ без ведома оператора, однако практического значения указанное решение Верховного суда так и не получило. 8. В текущей конфигурации основная выгода от использования СОРМ, к сожалению, достается производителям специальных технических средств, которые навязываются оператора в принудительном порядке в условиях неконкурентного рынка. Отношение "цена/результативность" СОРМ-2 остается очень невысокой. 9. Мне представляется, что скрупулезное исполнение Уголовно-процессуального законодательства и законодательства об оперативно-разыскной деятельности является вполне достаточным для обеспечения конституционных прав и свобод граждан. К сожалению, очень часты случаи использования СОРМ без санкции суда и без ведома оператора связи, что, разумеется, не соответствует Конституции РФ и должно пресекаться органами прокуратуры. Однако, мне неизвестны случаи прокурорского реагирования по фактам неправомерного использования СОРМ. И это весьма печально. 10. Отвечу кратко: такие случаи уже есть. И пока государство не предпримет жестких уголовно-правовых мер по отношению к недобросовестным оперативным работникам эта ситуация не изменится. Во всяком случае, гарантию конфиденциальности информации может дать только качественная криптографическая защита. 11. Термин СОРМ-3, так же, как и термин СОРМ-2, придуман журналистами. В нормативных правовых актах такая терминология не используется. Разумеется, СОРМ будет развиваться и приобретать все более глобальный и всеобъемлющий характер. Поэтому любителям частной жизни следует запасаться криптографическими средствами - с одной стороны, и не использовать сети связи для противоправной деятельности с другой стороны. 12. Полученные в результате использования СОРМ сведения являются допустимыми доказательствами в суде - но только при условии, если оперативные мероприятия осуществлялись в соответствии с требованиями уголовно-процессуального кодекса и законодательства об оперативно-разыскной деятельности. 13. Для операторов связи СОРМ совершенно невыгоден и не может быть выгоден по своей экономической природе. Фактически, все затраты на создание и поддержание СОРМ относятся на себестоимость услуг и, в конечном итоге, увеличивают цену услуг, хотя, как я уже говорил, закон возлагает на федеральный бюджет расходы, связанные с обеспечением СОРМ. Затраты на поддержание СОРМ зависят от масштаба деятельности оператора и могут достигать сотен тысяч и даже миллионов рублей в месяц. Впрочем, на практике эксплуатационные расходы на СОРМ оказываются невелики. 14. Значительная часть нормативной техническая документации в области СОРМ относится к сведениям, составляющим государственную тайну. Что, разумеется, существенно ограничивает


возможность конкуренции на данном рынке. В настоящее время такой конкуренции фактически нет. 15. Управление СОРМ осуществляется непосредственно из помещений УФСБ по выделенному каналу связи. На практике, используют и каналы IP/VPN, что существенно снижает уровень защищенности системы. Однако, ввиду технической невозможности глобальной фильтрации всего трафика, возможность несанкционированного ФСБ и оператором съема информации с СОРМ представляется несущественной. Есть гораздо более простые и эффективные способы незаконного проникновения в информационные системы операторов связи.

by, Антонов Игорь aka Spider_NET E-mail: antonov.igor.khv@gmail.com


Turn static files into dynamic content formats.

Create a flipbook
Issuu converts static files into: digital portfolios, online yearbooks, online catalogs, digital photo albums and more. Sign up and create your flipbook.