@ mrim.pl: Написание скриптов, работающих по протоколу MMP by Digimortal [ intr0 ] Что такое Mail.Ru Агент, я думаю, знают все. Для тех кто не в курсе, это асикуподобный мессенджер с поддержкой разнообразных дополнительных возможностей, вроде отправки смс, голосового общения, игр и пр. В этой статье я достаточно подробно опишу основные моменты работы с протоколом, который испульзует Мэйл.ру Агент, что поможет тебе в написании различных тулз, работающих с этой системой (ни в коем случае не пишите смс-флудеры, МАгент-спаммеры или сборщики почтовых баз mail.ru!!! ) Как уже можно было понять из названия, примеры кода, приводящиеся в статье, будут написаны на самом классном скриптовом языке программирования . [ протокол MMP ] Mail.Ru Агент использует собственный протокол - MMP, или mrim, который является частично открытым. На сайте agent.mail.ru выложено краткое описание протокола. В данном cписании присутствует только информация по основным возможностям мессенджера, но вооружившись сниффером пакетов, выяснить значения заголовков пакетов, например, для отправки смс, не составит труда. Помимо описания там присутствует С-хидер, с значениями полей, флагов и т.п. Я посчитал, что будет удобней объединить описание пакетов и этот заголовочный файл в один txt-файл, что и я сделал (смотри в ссылках). В дальнейшем думаю внести туда описания некоторых пакетов, которые не вошли в официальное описание. MMP действует поверх установленного tcp-соединения. Клиент инициализирует соединение с сервером, и далее взаимодействие происходит путем обмена сообщениями, причем сообщения могут отпарвляться как клиентом так и сервером. MMP является бинарным протоколом, кроме того, данные передаются не в общепринятом сетевом формате, а в little-endian'е, т.е. старший байт идет впереди. Основные типы данных, описанные в протоколе это: - UL; - LPS; - UIDL. Типом UL разработчики обозначили u_long или двойное слово, т.е. 4 байта. LPS - это составной тип, в который кодируются текстовые строки. Он представляет собой идущий впереди UL, в котором содержится длина строки, и саму строку. Строки представлены в windows-1251 кодировке. UIDL используется гораздо реже первых двух и в статье затрагиваться не будет. Представлен последовательностью из 8 символов из множества [a-z A-Z 0-9 _ - = +]. [ Структура пакетов ] Рассмотрим теперь структуру пакетов протокола. Как и полагается, пакет состоит из заголовка и данных (данные могут и отсутствовать). Поля хидера: Code: <-4bytes-> ,----------. | magic | мэйджик +----------+ | proto | версия протокола +----------+ | seq | номер сообщения в текущем соединении (ответ будет иметь тот же номер) +----------+ | msg | тип пакета +----------+ | dlen | длина данных (без учета заголовка) +----------+ | from | ip отправителя в inet_aton() формате +----------+ | fromport | порт отправителя +----------+ | | зарезервированные 16 байт, которые +- -+ в текущих версиях протокола не используются | reserved | +- -+ | | +- -+ | | +==========+ | data | далее идут данные (если есть)... '~~~~~~~~~~' Самым важным для нас полем является "тип пакета". "Мэйджик" и "версия" у нас постоянны. "Номер сообщения" и "длина данных" подсчитуются по ходу дела, а правильный ip и port, как я понял, передавать вообще не обязательно, поэтому не спрашуй у меня, почему я заполняю их нулями (с значением поля seq тоже дозволены некоторые вольности). Тип пакета - это команды (или ответы на них), которыми обмениваются между собой клиент и сервер. Команды могут иметь параметры, передающиеся как данные пакета. Значения этих команд и параметров иможно взять из того файла, что прикрепил к статье. Я сразу приведу здесь те, что будут использоваться в приводимых далее примерах: Code: my $CS_MAGIC = 0xDEADBEEF; ## Клиентский Magic my $PROTO_VERSION = 0x1000A; ## Версия протокола my $MRIM_CS_HELLO = 0x1001; ## C->S, empty my $MRIM_CS_HELLO_ACK = 0x1002; ## S->C, UL mrim_connection_params_t my $MRIM_CS_LOGIN2 = 0x1038; ## C->S, LPS login, LPS password, UL status, LPS uagent my $MRIM_CS_LOGIN_ACK = 0x1004; ## S->C, empty my $MRIM_CS_LOGIN_REJ = 0x1005; ## S->C, LPS reason my $MRIM_CS_PING = 0x1006; ## C->S, empty my $MRIM_CS_USER_STATUS = 0x100F; ## S->C, UL status, LPS user my $STATUS_ONLINE = 0x00000001; my $MRIM_CS_MESSAGE = 0x1008; ## S->C, UL flags, LPS to, LPS message, LPS rtf-message my $MESSAGE_FLAG_NORECV = 0x00000004; Пишем саб, формирующий пакет (правильнее будет сказать, формирующий его хидер - данные, которые ему передаются, должны быть уже заранее правильно сформированы): Code: sub make_mrim_packet { my ($msg, $data) = @_; ## получаем параметры my ($magic, $proto, $seq, $from, $fromport) = ($CS_MAGIC, $PROTO_VERSION, $seq_real, 0, 0); my $dlen = 0; ## длина данных равна 0 или $dlen = length($data) if $data; ## если есть данные, то рассчитывается их длина my $mrim_packet = pack("L11", $magic, $proto, $seq, $msg, $dlen, $from, $fromport, 0, 0, 0, 0); ## пакуем заголовок шаблоном "L" $mrim_packet .= $data if $data; ## добавляем данные, если они есть return $mrim_packet; ## возвращаем готовый к отправке пакет } [ Взаимодействие client <-> server ] Для того, чтоб устаовить соединение с mrim-сервером, нужно прежде получить его ip и port. Для этого необходимо установить tcp-соединение с mrim.mail.ru:2042 или mrim.mail.ru:443. Проделав это, клиент получает рекомендуемый для соединения ip-адрес и порт. Итак, вот саб, возвращающий iport для коннекта: Code: sub get_host_port { my $sock1 = new IO::Socket::INET ( PeerAddr => 'mrim.mail.ru', PeerPort => 2042, ## как вариант можно использовать 443 PeerProto => 'tcp', TimeOut => 10 ); sysread ($sock1, my $answ, 18); close $sock1; chomp $answ; return split /:/, $answ; } Получив ip-адрес и порт, мы можем создавать соединение с сервером для прохождения авторизации, которая происходит следующим образом: Code: ,---. ,---. | C |---------MRIM_CS_HELLO------->| S | | L |<------MRIM_CS_HELLO_ACK------| E | | I | | R | | E |--------MRIM_CS_LOGIN2------->| V | | N |<------MRIM_CS_LOGIN_ACK------| E | | T |(или <---MRIM_CS_LOGIN_REJ---)| R | `---' `---' Клиент посылает пакет "HELLO", в ответ получает "HELLO_ACK" с данными (UL) в которых содержится ожидаемая частота пинга (ping_period). Клиент должен пинговать сервер специальным пакетом через интервал, равный ping_period секунд. Сервер может изменять ping_period, посылая специальный пакет клиенту, но обычно ping_period равен 30 секундам. Напишем саб для отпавления HELLO-пакета и получения значения ping_period из него: Code: sub hello { print $sock make_mrim_packet($MRIM_CS_HELLO); sysread ($sock, my $ack, 48); ## принимаем 48 байт (44 - хидер, 4 - данные) my ($magic, $proto, $seq, $msg, $dlen, $from, $fromport, $r1, $r2, $r3, $r4, $data) = unpack ("L12", $ack); $ping_period = $data; ## получаем значение ping_period $seq_real++; ## $seq_real - это в моем коде счетчик seq print "[+] connected..\n" if $data; } Заодно напишем и саб для осуществления пинга сервера. Он будет очень простым: Code: sub ping { print $sock make_mrim_packet($MRIM_CS_PING); $seq_real++; } Теперь нужно переслать серверу пакет MRIM_CS_LOGIN2, который содержит информацию, необходимую для авторизации: LPS login, LPS password, UL status и LPS user_agent. login и password - это, понятно что, статус - тоже, а user_agent - это описание клиента, может быть любым. При удачной авторизации сервер отвечает нам пакетом MRIM_CS_LOGIN_ACK, при неудачной - MRIM_CS_LOGIN_REJ, в данных которого содержится причина отказа в авторизации. Теперь воплотим это в коде: Code: sub login { my $data = pack ("L", length($login)) . $login . ## упаковываем LPS-данные pack ("L", length($password)) . $password . pack ("L", $status) . ## а вот так UL pack ("L", length($user_agent)) . $user_agent; print "[~] try to login as $login:$password\n"; print $sock make_mrim_packet($MRIM_CS_LOGIN2, $data); ## посылаем пакет $seq_real++; ## не забывая про счетчик сообщений sysread ($sock, my $ack, 48); ## считываем ответ my ($magic, $proto, $seq, $msg, $dlen, $from, $fromport, $r1, $r2, $r3, $r4, $data_ack) = unpack ("L12", $ack); ## и распаковываем его if ($msg == $MRIM_CS_LOGIN_ACK) ## проверяем удачно ли прошла авторизация { print "[+] authorization succesfull\n"; } elsif ($msg == $MRIM_CS_LOGIN_REJ) { print "[-] bad authorization:$data_ack\n"; } else { print "[!] something wrong!\n"; } } [ Ложки нету =) ] По сути, у нас есть уже все необходимое, чтоб вывести в онлайн наш mrim.pl. Но просто висящий в онлайне скрипт - это совсем неинтересно, и я решил добавить в статью код, отправяляющий сообщение на указанный адрес. Саб этот я упростил до безобразия, не сделав возможность устанавливать флаги, не сделав проверку на получение адресатом сообщения и еще многие вещи, которые можно было бы сделать (впрочем, практически во всех вышеприведенных сабах стоило бы доработать некоторые моменты). Данные которые должен содержать пакет сообщения: UL flags, LPS to, LPS message, LPS rtf-message. Установив нужные флаги, можно указать тип сообщения (например, указать, что пересылаемые данные являются списком контактов). Я установил только флаг, означающий отсутствие необходимости присылать пакет о подтверждении доставки сообщения. Сообщение можно оформить и в rtf-формате, но мне это нафиг не нужно, потому в rtf-message отправляется ноль. Итак саб, отправляющий сообщение: Code: sub message { my ($to, $text) = @_; ## получаем мыло адресата и текст сообщения my $data = pack ("L", $MESSAGE_FLAG_NORECV) . ## выставим нужный флаг pack ("L", length($to)) . $to . pack ("L", length($text)) . $text . pack ("L", '0'); print $sock make_mrim_packet($MRIM_CS_MESSAGE, $data); $seq_real++; } Теперь соберем все это воедино, добавив основной код: Code: #!/usr/bin/perl use strict; use warnings; use IO::Socket::INET; ## config ## my $login = '[email protected]'; ## логин my $password = 'g00dp4ss'; ## пасс my $user_agent = 'mrim.pl'; ## описание агента ## constants ## ниже вставь константы из первой врезки с кодом < ...constants here... > ## vars ## my $seq_real = 0; ## счетчик комманд my $ping_period = 30; ## интервал для пинга my $status = $STATUS_ONLINE; ## статус ## main ## my ($host, $port) = get_host_port(); ## берем хост:порт print "[~] connecting to $host:$port..\n"; my $sock = new IO::Socket::INET ## коннектимся ( PeerAddr => $host, PeerPort => $port, PeerProto => 'tcp', TimeOut => 10 ); hello(); ## хеллоу и .. login(); ## логинимся sleep $ping_period; ## просто повисим в онлайне с полминуты ping(); ## пинг message('[email protected]', 'There is no spoon, Neo..'); ## отправляем сообщение на [email protected] sleep $ping_period; ## продолжаем еще некоторое время на- ping(); ## ходиться в онлайне sleep 10; ## subs ## а ниже добавь все сабы, которые присутствовали в статье < ...subs here... > ## eof.. Запускаем: Code: D:\perl-mrim>perl mrim.pl [~] connecting to 194.67.57.244:2041.. [+] connected.. [~] try to login as [email protected]:g00dp4ss.. [+] authorization succesfull Наблюдая за [email protected] из официального mrim-клиента, видим как он выходит в онлайн, а затем получаем от него сообщение: "There is no spoon, Neo.." =) [ outr0 ] На этом все, т.к. приведенной информации уже вполне достаточно для того, чтоб ты мог начать писать свои тулзы, взаимодействующие с системой МАгент. Жду теперь интересных релизов по этой теме.. [ Links ] http://agent.mail.ru - официальный сайт Mail.ru Агент http://agent.mail.ru/dev-license.html - официальное описание протокола MMP http://digimortal.0x48k.cc/articlz/mrim-packets.txt - отредактированное мной описание пакетов mrim http://hellknights.void.ru - сайт моей тимы http://0x48k.cc - форум DarkSide ResearcherZ Специально для форума Античат..
Весьма познавательно) Насчет кода: я никак не пойму, зачем многие люди юзают sysread/write ? Ты вроде юзаешь IO::Socket, так почему нельзя обойти просто $sock->send/recv ? Тем более, что sysread/write могут вызывать проблемы в некоторых ситуациях... ЗЫ: Тем не менее, этот человек разобрался в протоколе довольно глубоко)
KSURi упорно подражает Perl Underground))) шутко зачем ты придераешься к коду? ведь фишка статьи обьяснить работу протокола а не показать примеры красивого кода. Обычный пример. я думаю ты излишне строг.
Хорошо. Многим будет интересно. В своё время разобрался в этом протоколе и очень полезный опыт получил. Никогда до того бинарными протоколами не занимался.
тут ты прав.. немного позже поправлю код.. (у меня почему-то уже вошло в привычку использовать IO::Socket тока для открытия сокета =/).. да нет, KSURi все верно говорит.. ) и Perl Underground - это тру ).. если не обращать внимание на "мелкие" погрешности в своем коде, то они со временем становятся привычкой, и каждый твой скрипт будет в итоге говном.. а код просто обязан быть красивым, имхо =)
Не совсем верно. Я взял себе за правило писать на более-менее хорошем тоне, а не подражать Perl Underground. Езин всего лишь толкает хороший тон в массы, он его не изобрел. А по поводу sysread/write: Т.е. при использовнии sysread и print (или syswrite и while <$sock>) могут получиться проблемы. За исследование протокола я уже сказал спасибо в виде +мах. Жду исследований на тему отправки смс
кстати, я тока щас обратил внимание на то, что зарезервированные поля похоже все же используются в текущей версии протокола: Code: EF BE AD DE 0D 00 01 00 00 00 00 00 02 10 00 00 я╛н▐............ 04 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ B8 21 E7 0E 44 65 86 BF A8 1F 86 BF 1E 00 00 00 ╕!ч.DeЖ┐и.Ж┐.... EF BE AD DE 0D 00 01 00 01 00 00 00 04 10 00 00 я╛н▐............ 00 00 00 00 00 00 00 00 00 00 00 00 08 00 00 00 ................ 1F 00 00 00 0A 00 00 2B 07 F8 00 00 .......+.°..
А не знаете ещё сниферов, что б видеть трафф только одной проги, а не всех? и кста, не могу найти в пакете место где находится номер пакета (в коде $seq_real)
угу, спасибо вот ещё фишка когда в клиент мессдж идёт, надо выслать потверждение. знаешь как? что то я пока не разберусь
гляди в описании пакетов, там все это расписано.. и если ты вникнешь в содержимое статьи, то все будет предельно ясно: Code: [ Доставка сообщения (sc): MRIM_CS_MESSAGE_ACK = 0x1009 ] UL msg_id ## Номер пакета (Sequence) этого сообщения для отправителя UL flags ## Возможные значения описаны в MRIM_CS_MESSAGE LPS from ## Адрес отправителя LPS message ## текстовая версия сообщения LPS rtf-message ## форматированная версия сообщения [ Подтверждение получения сообщения (cs): MRIM_CS_MESSAGE_RECV = 0x1011 ] LPS from UL msg_id ~Отправляется получателем сообщения сразу после прихода MRIM_CS_MESSAGE_ACK, если флаги MRIM_CS_MESSAGE_ACK не содержали MESSAGE_FLAG_NORECV. from и msg_id должны быть скопированы из MRIM_CS_MESSAGE_ACK и имеют то же значение. тоесть, получив сообщение (MRIM_CS_MESSAGE_ACK), ты должен вытащить из данных LPS from, UL msg_id и отправить их как данные в пакете MRIM_CS_MESSAGE_RECV. Все это можно сделать по аналогии с уже приведенными в статье примерами кода..
мне сразу несколько приходит и хз откуда выдирать. точнее думаю с первого, но не могу догнать что за остальные пакеты.
это все один пакет ) просто в примерах в статье я всегда знал сколько будет размер получаемого пакета и выставлял его в sysread, но в твоем случае размер пакета заранее неизвестен - ведь данные могут быть различной длины.. у тебя первое - это заголовок - из него можно взять длину данных 1A 01 00 00 = 11Ah = 282 байта, а затем принять эти 282 байта - это будут данные пакета.. из этих данных и брать нужные значения..