книги хакеры / журнал хакер / 116_Optimized
.pdf
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|||
|
|
X |
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
||
|
F |
|
|
|
|
|
|
t |
|
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
|
NOW! |
o |
|||
|
|
|
|
|
|
|
||||
|
|
|
|
|
BUY |
|
|
|||
|
|
|
|
to |
|
|
|
|
|
|
w Click |
|
|
|
|
|
m |
||||
|
|
|
|
|
|
|||||
w |
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
-xcha |
|
|
|
|
|
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
|
C |
E |
|
|
||||
|
|
|
X |
|
|
|
|
|
|||
|
|
- |
|
|
|
|
|
d |
|
||
|
|
F |
|
|
|
|
|
|
t |
|
|
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
|
r |
||
|
P |
|
|
|
|
|
NOW! |
o |
|||
|
>> codingto BUY |
|
|
||||||||
|
|
|
|
|
|
||||||
|
w Click |
|
|
|
|
|
|
m |
|||
|
w |
|
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
o |
|
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
|
-x cha |
|
|
|
|
||
|
|
|
|
|
|
|
|
|
|
|
|
Скудная страница с официальным описанием MMP
Простецкий дизайн будущей проги
подключения. Кстати, именно этой простой фишки лишен Mail.Agent, так |
|
|
• 1 компонент TMainMenu |
|
|
что — наматывай на ус (А нафига это надо? — Прим. ред.). |
• 1 TImageList |
|
OnDisconnect— дисконнект он и в Африке дисконнект. |
• 1 TMrim |
|
OnGetFile— информирует о приеме файла от контакта. |
• 1 TListView |
|
OnList— возникает при получении списка контактов. |
• 1 TProgressBar |
|
OnListUpdate— происходит при обновлении контактов. |
|
|
|
|
|
OnLoginInfo— срабатывает во время подключения к серверу. Например, |
По всей форме я растянул компонент TListView. В нем будет отображать- |
|
если не получилось подключиться из-за неправильно введенных данных |
ся контакт-лист. Чтобы контакты не смотрелись серо и убого, я подобрал |
|
(логин, пароль), то тут это можно обработать. |
в TImageListкартинки, соответствующие возможным статусам пользо- |
|
OnMeInfo—событиевозникаетприполученииинформацииотвоемаккаунте. |
вателей (Online, Away, Offline и т.д.). С помощью компонента TMainMenu |
|
OnMessage— событие генерируется во время получения очередного |
я создал основное меню из следующих пунктов: подключить, отключить, |
|
сообщения. |
выбор статуса (онлайн, отошел, невидимый). Готовый вид моей формы ты |
|
OnMyServicesMessage— при получении сервисных сообщений будет |
можешь увидеть на рисунке. |
|
генерировать это событие. |
На этом о форме можно забыть и приступить к кодингу. Первым делом |
|
OnNewMail— событие возникает в момент прихода в твой почтовый ящик |
научимся устанавливать и разрывать соединение с сервером. Для этого |
|
новой корреспонденции. |
создай обработчик события OnClickдля кнопки c именем «Подключить» и |
|
OnRecvNormalAvatar— событие возникает во время приема аватары |
напиши в нем: |
|
контакта. Здесь можешь написать код для обновления аватары у нужного |
|
|
контакта. |
If Form2.ShowModal = mrCancel Then |
|
OnRecvSmallAvatar— по сути, то же самое, что и предыдущее, за исключе- |
Exit; |
|
нием типа аватары. Событие возникает при приеме маленькой аватары. |
ConnectBar.Visible := true; |
|
OnSecondLogin— событие сигнализирует о неприятном известии: под |
|
|
твоим логином кто-то вошел. Во время возникновения этого события нужно |
MailAgent.Login := Form2.LoginEdit.Text; |
|
поднимать тревогу и убеждать пользователя скорее сменить пароль. |
MailAgent.Password := Form2.PassEdit.Text; |
|
OnStatus— событие возникает при изменении каким-нибудь контактом |
MailAgent.LoginStatus := STATUS_ONLINE; |
|
своего статуса. |
|
|
OnUserSearchResult— событие генерируется при получении результатов |
MailAgent.login_s__desc := 'Я в сети!!!'; |
|
поиска. |
MailAgent.Connect2(MailAgent.Login, MailAgent. |
|
|
Password); |
Практика
Толку от неподкрепленной практикой теории мало. Поэтому запусти Delphi и приготовься кодить. Как обычно, сразу после запуска Delphi создаст новый пустой проект. Закрой его и загляни на наш DVD. Там тебя ждет компонент TMRim. Скопируй его на винт и заинсталь к своему Dlephi. Компонент устанавливается стандартным способом через Component Install Component. Установив компонент, создай новый проект и накидай на форму следующие компоненты:
Перед тем, как начать попытку подключения, я модально показываю Form2 (смотри рисунок). На этой форме расположены два поля ввода, в которые нужно ввести имя пользователя и пароль. Если пароль и имя пользователя введены успешно, то можно начинать первую попытку соединения. Для этого я заполняю все необходимые свойства компонента TMrim(у меня он носит имя MailAgent). После того, как все поля заполнены, можно выполнить метод Connect2.
xàêåð 08 /116/ 08 |
099 |
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
C |
E |
|
|
||||
|
|
X |
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
||
|
F |
|
|
|
|
|
|
t |
|
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
NOW! |
o |
||||
|
|
|
|
|
|
|||||
|
|
|
|
|
|
|
||||
w Click |
to BUY |
|
>> coding |
|||||||
|
|
|
|
|
|
m |
||||
w |
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
-xcha |
|
|
|
|
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|||
|
|
X |
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
||
|
F |
|
|
|
|
|
|
t |
|
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
|
NOW! |
o |
|||
|
|
|
|
|
|
|
||||
|
|
|
|
|
BUY |
|
|
|||
|
|
|
|
to |
|
|
|
|
|
|
w Click |
|
|
|
|
|
m |
||||
|
|
|
|
|
|
|||||
w |
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
-x cha |
|
|
|
|
Продвинутый
интерфейс спомощьюодной функции
procedure TForm1.CreateNewTab( user_email: string; text:string);
var
_NewTab : TTabSheet; _NewTextMemo, _NewChatMemo:TMemo; i:integer;
begin
for i:=0 to form3.PageControl1.PageCount-1 Do if form3.PageControl1.Pages[i].Caption =
user_nick then begin
form3.PageControl1.ActivePageIndex := i; if Text <> '' then
TMemo(Form3.PageControl1.Pages[i]. FindComponent(ChatMemo+IntToStr(i+1))). Lines.Add(user_nick + ': ' + text);
form3.Show;
Exit;
end;
_NewTab := TTabSheet.Create(form3.PageControl1); _NewTab.Caption := user_nick;
_NewTab.Name := 'Tab'+IntToStr (form3.PageControl1.PageCount); _NewTab.PageControl := form3.PageControl1; _NewChatMemo := TMemo.Create(_NewTab); _NewChatMemo.Name := ‘ChatMemo’+IntToStr( Form3.PageControl1.PageCount); _NewChatMemo.Align := alTop; _NewTab.InsertControl(_NewChatMemo); _NewTextMemo := TMemo.Create(_NewTab); _NewTextMemo.Name := 'TextMemo' + IntToStr( form3.PageControl1.PageCount); _NewTextMemo.Align := alTop; _NewTab.InsertControl(_NewTextMemo); _NewChatMemo.Lines.Clear; _NewTextMemo.Lines.Clear;
If Text <> '' Then _NewChatMemo.Lines.Add(user_nick + ': ' + text);
Form3.PageControl1.ActivePage := _NewTab; Form3.Show;
end;
Элегантная форма запроса логина и пароля
Процесс подключения запущен. Было бы неплохо иметь возможность отследить его текущее состояние на ProgressBar. Создай для компонента TMrimобработчик события OnConnectProgressи напиши в нем:
ConnectBar.Position := State; If State = 100 Then begin Sleep(100); ConnectBar.Visible := false; end;
Думаю, пояснять этот код нет смысла. Сложного в нем абсолютно ничего. Двигаемся дальше. С подключением разобрались, теперь нужно позаботиться о заполнении контакт-листа. Тебе уже известно, что получение списка контактов происходит при возникновении события OnList-компонента TMrim, а раз так — создай обработчик события OnListи напиши в нем всего лишь две строчки кода:
_ContactList := List;
GetUserList ();
В первой строчке кода я получаю в переменную _ContactList(объявлена как глобальная переменная типа AContact_List) ссылку на весь список контактов (List). После того, как ссылка получена, я обращаюсь к самописной процедуре GetUserList(), код которой ты можешь увидеть на соответствующей врезке.
Получаеминдекс статусной картинки
function TForm1.GetStatusImage(status: integer; advStatusId:string): integer;
begin Result := 1;
Case Status Of STATUS_ONLINE: Result := 0; STATUS_OFFLINE: Result := 1; STATUS_AWAY: Result := 2;
STATUS_FLAG_INVISIBLE : Result := 5; STATUS_USER_DEFINED:
begin
if advStatusId = 'status_chat' then Result := 3; if advStatusId = 'status_dnd' then Result := 4;
end;
end;
end;
100 |
xàêåð 08 /116/ 08 |
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|||
|
|
X |
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
||
|
F |
|
|
|
|
|
|
t |
|
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
|
NOW! |
o |
|||
|
|
|
|
|
|
|
||||
|
|
|
|
|
BUY |
|
|
|||
|
|
|
|
to |
|
|
|
|
|
|
w Click |
|
|
|
|
|
m |
||||
|
|
|
|
|
|
|||||
w |
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
-xcha |
|
|
|
|
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
C |
E |
|
|
|||
|
|
X |
|
|
|
|
|||
|
- |
|
|
|
|
d |
|
||
|
F |
|
|
|
|
|
t |
|
|
|
D |
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
NOW! |
o |
|||
>> codingto BUY |
|
|
|||||||
|
|
|
|
|
|||||
w Click |
|
|
|
|
|
m |
|||
w |
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
g |
|
|
|
|
|
|
df |
|
n |
e |
|
||
|
|
|
|
-x cha |
|
|
|
|
Тестируемклиента
Клиентготов,теперьсамоевремяегохорошенькопротестировать.Я |
|
|
|
|
специальнозавелпаруаккаунтовнаmail.ru:одиндлятолькочтоиспе- |
|
|
|
links |
ченногонамиклиента(user_test_2008@mail.ru),авторой—дляMail. |
|
|
|
Дополнительный |
Agentа(user_test_2009@mail.ru). Обаклиентауспешносоединились |
|
|
|
исходник (на чистом |
ссерверомиобменялисьнесколькимисообщениями.Результатты |
|
|
|
WinSock API) ты мо- |
можешьувидетьнарисунках(ссоответствующимиподписями). |
|
|
|
жешь скачать с сайта |
Оба клиента успешно соединились |
||||
|
с сервером |
|
|
www.vr-online.ru |
|
|
|
|
после выхода журнала |
|
|
|
|
в свет. |
|
|
|
|
|
Стороны обменялись сообщениями |
Будущий многооконный интерфейс |
Чтоможетбытьпроще, чемполучитьсписокконтактов?
procedure TForm1.GetUserList; var
i:integer; _User : AUser; begin
ListView1.Items.Clear;
for i:=0 to _ContactList.users_num-1 do begin
_User := _ContactList.users_id[i]; with ListView1.Items.Add do
begin
Caption := _User.user_nick;
ImageIndex := GetStatusImage(_User.user_ status, _user.user_status_id);
end;
end;
end;
чатасзакладками.Каждаятакаязакладка—эточатсопреде- леннымсобеседником.Оченьудобноипозволяетизбавитьсяот проблемы«завала»всегорабочегостола«окнами».Ярешилне отступатьотэтойидеииреализоватьвнашемпримереподобный интерфейс.Сейчасяподробнорасскажу,какэтоделается. Создай новую форму и брось на нее один компонент TPageControl(вкладки не создавай) и одну кнопку. У компонента TPageControlустанови свойство Alignв alTop (растянуть по верху). Теперь растяни этот компонент по всей оставшейся части формы, но не забудь оставить немного места под кнопку. По ее нажатию мы будем отправлять сообщения активному в данный момент собеседнику. Если ты запутался с расположением компонентов, то не парься, а просто взгляни на рисунок и по нему подгони свою форму.
Как закончишь с дизайном, возвращайся к модулю главной формы и объяви в ней новую процедуру: CreateNewTab (user_email:string; text:string), где user_email
— e-mail пользователя, от которого пришло (или которому хотим отправить) сообщение. Этот мыльник у нас будет
dvd
Как обычно, исходник примера, а также компонент можно взять на нашем диске.
Основная начинка процедуры GetUserList ()— пробежка по структуре типа AContact_List, содержащей список контактов, и добавление всех найденных контактов в ListView. Обрати внимание, что в примере из структуры я извлекаю лишь ники пользователей. При программировании реального приложения этим можно не ограничиваться. Например, можно получить дату рождения пользователя, знак зодиака, e-mail и т.д. (полное описание структуры смотри во врезке). Добавляя очередной контакт в ListView, я определяю его статус и уже на основании его устанавливаю соответствующую картинку. Чтобы облегчить этот процесс, я написал функцию GetStatusImage(). В качестве параметров ей нужно передать статус и идентификатор статуса пользователя. Результатом будет число, соответствующее определенному индексу картинки в TImageList. Исходный код функции
GetStatusImage() приведен во врезке.
Чатимся
Получитьсписокконтактоввкрасивомвиде—лишьполдела. ГлавнаяфункциялюбогоIM—предоставлениевозможностиком- фортногообщения,длячегопрограммистыобычносоздаютокно
Получаеминдекс статуснойкартинки
functionTForm1.GetStatusImage(status:integer;advStatusId:string):integer; begin
Result:=1;
CaseStatusOf STATUS_ONLINE:Result:=0; STATUS_OFFLINE:Result:=1; STATUS_AWAY:Result:=2;
STATUS_FLAG_INVISIBLE:Result:=5; STATUS_USER_DEFINED:
begin
ifadvStatusId=‘status_chat’thenResult:=3; ifadvStatusId=‘status_dnd’ thenResult:=4; end;
end;
end;
xàêåð 08 /116/ 08 |
101 |
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
C |
E |
|
|
|||
|
|
X |
|
|
|
|
|||
|
- |
|
|
|
|
d |
|
||
|
F |
|
|
|
|
|
t |
|
|
|
D |
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
NOW! |
o |
|||
|
|
|
|
|
|
||||
|
|
|
|
|
|
|
|||
w Click |
to BUY |
|
>> coding |
||||||
|
|
|
|
|
m |
||||
w |
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
g |
|
|
|
|
|
|
df |
|
n |
e |
|
||
|
|
|
|
-xcha |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Описаниеважных структур
AContact_List
AContact_List=record //Количествогрупп group_num:integer; //МассивструктуртипаAGroup
groups_id:array[0..20]ofAGroup; //МассивструктуртипаAUser users_id:array[0..1023]ofAUser; //Общиеколичествоконтактов users_num:integer;
AUser=record
user_flags:integer; user_gnum:integer; //Emailпользователя user_mail:string; //Nickпользователя user_nick:string; //Имяпользователя user_name:string; //Фамилияпользователя user_lastname:string; //Датарождения user_bday:string; //Местожительства user_location:string; //Флаги
server_flags:integer; //Статуспользователя user_status:integer; //Телефон user_phone:string;
//Идентификаторпользователя user_id:integer; //Идентификаторстатуса user_status_id,
//статуспользователя user_status_name, //Оисаниестатуса user_status_desc:string;
//Идентификаторклиентапользоваля user_client_id:string;
end;
отображаться в качестве заголовка закладки, тем самым, идентифицируя собеседника. Смотрится не очень красиво (в реальном приложении лучше отображать ник пользователя). Весь код этой незатейливой процедуры ты можешь увидеть на соответствующей врезке. Быстренько начинай его
переписывать, не забывая при этом иногда возвращаться к тексту статьи за разъяснениями.
Изначально мы не можем знать точного количества собеседников, для которых в окне чата будем создавать закладки. Поэтому новые вкладки логичней всего создавать динамически — по мере необходимости. Скорее всего, после прочтения последнего предложения тебе стало смешно,
— мол, как еще в такой ситуации можно поступить. Оказывается, можно. Однажды мне попался исходник довольно симпатичного асечного клиен-
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|||
|
|
X |
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
||
|
F |
|
|
|
|
|
|
t |
|
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
|
NOW! |
o |
|||
|
|
|
|
|
|
|
||||
|
|
|
|
|
BUY |
|
|
|||
|
|
|
|
to |
|
|
|
|
|
|
w Click |
|
|
|
|
|
m |
||||
|
|
|
|
|
|
|||||
w |
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
-x cha |
|
|
|
|
та. Когда я принялся его изучать и добрался до формы ями, — я ужаснулся. Автор программы создал почти сотню (!) вкладок и по
мере необходимости игрался со свойством Visible. Но что-то я немного отвлекся. Поскольку все элементы формы сообщений мы будем создавать динамически, то на первом этапе нам нужно определить, какие потребуется создавать элементы управления. Обычно в подобных окнах размещают пару компонент типа TMemo (TRichEdit и всевозможные клоны). Один используется для отражения всей истории переписки, а второй — для ввода текста отправляемого сообщения. Помимо элементов ввода нам придется создавать новую закладку в компоненте PageControl. Итак, подведем итог. Для создания новой вкладки нам нужно создать три компонента: два TMemo и один TTabSheet. По этой причине в разделе var моей процедуры я объявил три соответствующие переменные: _NewTab
типа TTabSheet (новая закладка для PageControl), _NewTextMemo
типа TMemo (ввод текста для отправки) и _NewChatMemo (для отображения всей переписки) аналогичного типа. По моему замыслу, эта процедура должна вызываться каждый раз при получении нового сообщения. Следовательно, перед созданием новой вкладки нужно удостовериться, что для автора полученного сообщения еще не была создана закладка.
Для проверки я запускаю цикл (for i:=0 to form3.PageControl1. PageCount-1), которой пробегает по всем существующим вкладкам и сравнивает их заголовок с e-mail отправителя. Если совпадение найдено, то нет смысла создавать еще одну закладку. Нужно просто добавить в компонент ChatMemoX полученный текст. Поскольку полного имени компонента (вместе с индексом) мы не знаем, то нам придется его найти с помощью метода FindComponent() элемента, на котором мы, собственно, и ищем компонент. В нашем случае компонент ChatMemo будет располагаться на компоненте TPageControl, именно поэтому метод
я вызываю следующим образом: Form3.PageControl.Pages[i]. FindComponent('ChatMemo'+ IntToStr(i+1)). В качестве одного единственного параметра для метода FindComponent() я передаю имя искомого компонента, плюс его индекс, который будет равен индексу текущей закладки + 1 (так как первая вкладка имеет индекс 0). Компонент найден, — теперь надо добавить в него полученный текст. Чтобы это сделать, я привожу полученный в результате поиска объект к типу TMemo, а затем вызываю метод свойства Lines — Add(). Дальше все просто
— добавив полученный текст, вызываю метод Show формы сообщений и выхожу из процедуры. Мы рассмотрели вариант с найденной вкладкой. А если она еще не была создана? Конечно же, нужно ее создать! Порядок очереди создания новых элементов будет таким:
1.Новая вкладка (TNewTabSheet);
2.Новый элемент хранения истории переписки (TNewChatMemo); 3.Новый элемент для ввода текста для отправки (TNewTextMemo). Каждый визуальный компонент в Delphi — это обычный объект, который первым делом нужно инициализировать (выделить память). После можно заполнять все свойства, прикручивать обработчики событий и т.д. Единст венный нюанс при создании визуальных компонент — необходимость «вставки» (указание родителя) этого компонента на любой другой. Иначе, как можно догадаться, — компонент не отобразится. Чтобы вставить вновь созданный компонент в форму, необходимо лишь вызвать у формы метод InsertControl(). В качестве единственного параметра нужно указать ссылку на проинициализированный элемент управления.
Happy End
ЛекциюрассмотрениявнутренностейпротоколаMMPможносчитатьоконченной.Тебеостаетсяпереваритьзнанияивоспользоватьсяимивсвоих хацкерскихцелях.ПолученнойинфывполнехватитдлянаписанияполноценногоMail.Agent’aилиспамбота.Ксожалению,спецификацияпротокола ужедавнонеобновлялась,азначит,шагатьвногусMail.Agent’ом,увы,не получится.Будемнадеяться,чтодевелоперыMAрасщедрятсяиполностью откроютпротоколсовсемивкусностями(передачавидеоданныхит.д.).Пока намостаетсялишьпользоватьсятем,чтоесть.Наэтойнемногогрустной нотеяхочупопрощатьсяипожелатьтебебольшепозитиваипоменьше
AccessViolition.Пока!
P.S.Автор выражает огромную благодарность Алексею Панову за идею статьи и отличный компонент для работы с MMP. z
102 |
xàêåð 08 /116/ 08 |
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|||
|
|
X |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
|
|
|
|
|
|
|
|
|
|
|
|
||
|
F |
|
|
|
|
|
|
t |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
r |
|
|
|
|
|
|
|
|
||||||
P |
|
|
|
|
|
NOW! |
o |
|
|
|
|
|
|
|
|
|||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||||
|
|
|
|
|
BUY |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|||
|
|
|
|
to |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
w Click |
|
|
|
|
|
m |
|
|
|
|
|
|
|
|
||||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|||||||||
w |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
o |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
. |
|
|
|
|
|
.c |
|
|
|
|
|
|
|
|
|
|
|
|
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
df |
|
|
n |
e |
|
|
|
|
|
|
|
|
|
|
|
|
|
||
|
|
|
|
-xcha |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|||
|
|
|
|
X |
|
|
|
|
|
|||
|
|
|
- |
|
|
|
|
|
d |
|
||
|
|
|
F |
|
|
|
|
|
|
t |
|
|
|
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
|
|
r |
||
|
|
P |
|
|
|
|
|
NOW! |
o |
|||
|
|
|
|
|
|
|
|
|
||||
|
|
|
|
|
|
|
BUY |
|
|
|||
|
|
|
|
|
|
to |
|
|
|
|
|
|
|
w Click |
|
|
|
|
|
m |
|||||
|
|
|
|
|
|
|
||||||
w |
|
|
|
|
|
|
|
|
|
|||
|
|
|
w |
|
|
|
|
|
|
|
o |
|
|
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
|
|
-x cha |
|
|
|
|
||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|||
|
|
X |
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
||
|
F |
|
|
|
|
|
|
t |
|
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
NOW! |
o |
||||
|
|
|
|
|
|
|||||
|
|
|
|
to BUY |
|
|
||||
w Click |
|
|
|
>> coding |
||||||
|
|
|
|
|
|
m |
||||
w |
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
df |
|
n |
e |
|
|||
|
|
|
|
-xcha |
|
|
|
|
Крис Касперски
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|||
|
|
X |
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
||
|
F |
|
|
|
|
|
|
t |
|
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
|
NOW! |
o |
|||
|
|
|
|
|
|
|
||||
|
|
|
|
|
BUY |
|
|
|||
|
|
|
|
to |
|
|
|
|
|
|
w Click |
|
|
|
|
|
m |
||||
|
|
|
|
|
|
|||||
w |
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
-x cha |
|
|
|
|
трюки открыса
Сишные трюки
Вольности, допускаемые Си/Си++ в отношении указателей (что отличает их от Java/.NET и других «правильных» языков), обеспечивают гибкость, компактность и высокое быстродействие целевого кода. Но подобная демократия таит в себе скрытую угрозу, и всякий указатель становится источником непредсказуемых побочных эффектов!
01Воднурекунельзявойтидважды?
Статическийанализотдельновзятойфункции(неважно—представ- ленныйввидеисходногокодаилидизассемблерноголистинга)справляется толькослокальнымипеременнымииобламываетсянауказателях,значение которыхневозможновычислитьнастадиитрансляции(указатель—неконс- танта)икоторыеобрабатываютсяужевrun-time.
Всякийнеконстантныйуказательспособенменятьлогикуработынетолько отдельновзятойанализируемойфункции,нодажеивсейпрограммывцелом! Разобраться,чтожедействительноделаеттотилиинойуказатель,можно толькоспомощьюотладчикаили…статическойтрассировкивсегоисходного текста—аэтофактическиравносильноисполнениюпрограммынаэмулиру- ющемотладчике.
Начинающиехакерынедооцениваютковарствоуказателей,зачтопотом расплачиваютсяизматывающейотладкой,отнимающейнамногобольше времени,чемкодирование.Хотитенаглядныйпример?Пожалуйста!
Исходныйкодзагадочнойфункции
foo(int *arg_a, int *arg_b) {
printf("1:-> %08Xh:%08Xh\n", *arg_a, *arg_b);
*arg_a = *arg_b; // (1)
printf("2:-> %08Xh:%08Xh\n", *arg_a, *arg_b);
*arg_a = *arg_b;
// (2) — может иметь другое действие, чем (1) printf("3:-> %08Xh:%08Xh\n", *arg_a, *arg_b);
*arg_a = *arg_b;
// (3) — может иметь другое действие, чем (1, 2) printf("4:-> %08Xh:%08Xh\n", *arg_a, *arg_b);
*arg_a = *arg_b;
// (4) — может иметь другое действие, чем (1, 2, 3) printf("5:-> %08Xh:%08Xh\n", *arg_a, *arg_b);
}
Казалось бы, такая простая функция foo() — всего четыре команды _a = *arg_b (отладочные вызовы printf не в счет). Разве не
очевидно, что здесь происходит копирование ячейки *arg_b в ячейку *arg_a, для «надежности» повторяемое четыре раза? Тогда почему оптимизирующие компиляторы (например, MS VC) даже на максимальном уровне оптимизации не выкидывают вторую и все последующие операции присвоения — в чем легко убедиться, заглянув в дизассемблерный листинг?
Предположение, что все команды «*arg_a = *arg_b» идентичны
— ошибочно. Оно базируется на неявном допущении, что arg_a и arg_b указывают на различные ячейки, чего нам никто не гарантирует. И что никаким боком не вытекает из анализа самой функции foo(), принимающей указатели arg_a и arg_b как аргументы. Понять, что же действительно здесь происходит, можно, только обратившись к материнской функции, которая в данном случае выглядит так:
Хитрыйвызовфункцииfoo()
впрограммеoverlapped-pointers.c
int buf[3]={0, — 1, 0}; main()
{
foo(buf, (int*)(((char*)buf) + 1));
}
Компилируем программу из командной строки, как обычно (cl.exe overlapped-pointers.c), запускаем и смотрим результат. По многочисленным просьбам читателей, не осиливших readme к Microsoft Visual Studio или запускающих vcvars32.bat из FAR’а, а не из отдельного cmd.exe, автор решил снабжать каждый приводимый листинг .dsw/. dsp-проектами, упрощающими сборку программы до предела (клавиша <F7> в Студии).
Новернемсякобсуждениюполученноговывода.Оннамногоинтереснее,чем этоможнопредположитьизанализаисходноготекста:
Результатработыпрограммыoverlapped-pointers.c
1:-> 00000000h:FF000000h
2:-> FF000000h:FFFF0000h
3:-> FFFF0000h:FFFFFF00h
4:-> FFFFFF00h:FFFFFFFFh
5:-> FFFFFFFFh:FFFFFFFFh
104 |
xàêåð 08 /116/ 08 |
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|||
|
|
X |
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
||
|
F |
|
|
|
|
|
|
t |
|
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
|
NOW! |
o |
|||
|
|
|
|
|
|
|
||||
|
|
|
|
|
BUY |
|
|
|||
|
|
|
|
to |
|
|
|
|
|
|
w Click |
|
|
|
|
|
m |
||||
|
|
|
|
|
|
|||||
w |
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
-xcha |
|
|
|
|
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
C |
E |
|
|
|||
|
|
X |
|
|
|
|
|||
|
- |
|
|
|
|
d |
|
||
|
F |
|
|
|
|
|
t |
|
|
|
D |
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
NOW! |
o |
|||
>> coding to BUY |
|
|
|||||||
|
|
|
|
m |
|||||
w Click |
|
|
|
|
|
||||
w |
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
g |
|
|
|
|
|
|
df |
|
n |
e |
|
||
|
|
|
|
-x cha |
|
|
|
|
Воттебеираз!Значениеячеек*arg_aи*arg_bменяетсявовсехчетырех итерациях,образуяузорнаподобие«елочки».Авсепотому,—чтофункции foo() переданыуказателинаперекрывающиеся(overlapped)ячейкипамяти, иоперацияприсвоенияменяетнетолькоприемник(target),ноиsource(источник)!Теперьпонятно,почемувозникает«елочка»:разприсвоениеменяет источник,топовторноеприсвоениедастинойрезультат.Точнее,можетдать,но можетинедать.Тутвсеотсодержимогоисточника/приемниказависит.
Вотпотомустатическийанализнауказателяхи«отдыхает».
02Хардкорныеизвратысадресомвозврата
Оправившись после «культурного шока», рассмотрим более сложный пример, — функцию baz(), состоящую из операции «*ret_addr = arg_a» (задействует один-единственный указатель). Ну и какого подвоха от нее ожидать? Да любого! Это же указатель! И писать он способен в абсолютно любую ячейку памяти, куда только разрешена запись. Может подменять адрес возврата из функции. Это используется для скрытой передачи управления многими защитными механизмами или представляет собой грязный «хак», вставленный сотрудником, который не хочет, чтобы коллеги понимали, как работает написанный им код.
Собственно говоря, сама функция baz() не делает ничего интересного. Все трюкачество сосредоточено в вызывающем коде, который в простейшем случае выглядит так — смотри «хитрый вызов функции foo() в программе overlapped-pointers.c». Вопрос: что выводит эта программа на экран? Даже динамический анализ с отладчиком в руках требует напряжения мозговых извилин и знания особенностей языка. Хинт: данный пример не закладывается на конкретный компилятор и сохраняет свою работоспособность даже при портировании на другие 32 битные системы. С формальной точки зрения, это не такой уж и грязный хак (примечание: для упрощения кода в программе использована ассемблерная вставка, но при желании можно реализовать и на чистом Си).
Исходныйкодпрограммысоскрытой подменойадресавозврата
//stdcall, since we need to blow up the args __stdcall bar (int arg_a)
{
static int count;
printf("%X:-> %08Xh:hello bar\n", ++count, arg_a);
}
//cdecl, since we don’t want to blow up the args
__cdecl baz (int arg_a, int *ret_addr)
{
*ret_addr = arg_a;
}
main()
{
foo(buf, (int*)(((char*)buf) + 1)); __asm
{
push eax
;for bar.RETN 4 (second pass, dummy arg) push offset next
;for bar.RETN 4 (second pass, jump to next) mov eax, esp
xàêåð 08 /116/ 08
; calculate the pointer to...
sub eax, 0Ch
..the return address of baz push eax
;for baz.ret_addr AND bar.RET 4 (dummy arg) push offset bar
;for baz.arg_a AND bar.RET 4 (jump to itself) call baz
;go-go bar baz :-)
next:
; don’t need SUB ESP,XX — stack is ok due to RET4
}
Компилируем программу так же, как и раньше (для экономии места она реализована все в том же файле overlapped-pointers.c), и смотрим на результат ее выполнения:
1:-> 0012FF60h:hello bar 2:-> 00000019h:hello bar
Мы морально подготовлены к тому, что после завершения baz() вызывается функция bar() (это вытекает из названия указателя ret_addr и явной засылке адреса bar командой push offset bar). Но тот факт, что bar() вызывается дважды — уже сюрприз! Говорю же, здесь не баг, а заранее просчитанный ход, который очень трудно распознать даже матерым программистам.
Отладчик покажет полную картину происходящего, а чтобы не сбиться с пути, автор даст несколько хинтов. Функция main() готовит стек, засовывая в него незначимый аргумент-пустышку (dummy arg), за которым следует адрес выхода из функции (смещение метки next). Далее засылается тщательно рассчитанное смещение адреса возврата из baz(), передаваемое как аргумент ret_addr и указатель на bar (аргумент arg_a).
И происходит вызов функции baz() с форсированной спецификацией cdecl-соглашения, определяющего порядок засылки аргументов в стек и снимающего с baz() обязанности по вычистке аргументов из стека после завершения. Команда «*ret_addr = arg_a;» подменяет адрес возврата из baz(), заменяя его указателем на функцию bar(), которая и вызывается при завершении baz(). Причем, стек остается в том же состоянии,
вкаком он был на момент вызова baz() — то есть с двумя аргументами: указателем на адрес возврата из baz(), ну, теперь уже bar(), и адресом самой функции bar(), которая (это очень важно!) форсирована на stdcallсоглашение,чтообязываетеевычищатьаргументыизстекапозавершению. Припервомвыполнениифункцииbar() онавыводитаргументarg_a (указательнаадресвозврата).Второйаргументтрактуетсякакадресвозврата
вматеринскуюфункцию.Вданномслучаетаковойявляетсясамаbar(), указательнакоторуюследуетзаarg_a.Следовательно,привыходеизфункции bar() онавыталкиваетarg_aизстекавместесадресомвозвратанасаму себя.Врезультатепроисходитееповторныйвызов,нотеперьнавершине стека—фиктивныйаргумент-пустышкаиуказательнаметкуnext,кудаи передаетсяуправление.
Такая вот замысловатая арабская вязь кода. Что тут сложного? После объяснения, конечно, ничего. Но сколько людей способны сказать, что делает эта программа по одним лишь исходным текстам без запуска ее на выполнение? z
105