И снова привет. Сегодня мы поговорим опять о MuOnline, на тему обращения открытых ключей шифрования, т.е. о способе получении закрытой пары ключей.
Чтобы ввести в курс дела немного опишу сам механизм шифрования сетевого трафика в MuOnline. Для передачи данных используются 2 вида пакетов, это открытые 0xC1, 0xC2 и зашифрованные 0xC3, 0xC4. Для шифрования же используется алгоритм на базе открытых\закрытых ключей. Клиент содержит открытый ключ enc1.dat и закрытый dec2.dat, сервер так же содержит вторую пару: открытый enc2.dat и закрытый dec1.dat. Банальная криптосистема с открытым ключом, которая встречается на версиях с 1 сезона и до MuEx, если я не ошибаюсь.
Шифрование ложится только на критические пакеты. Однако сам протокол игры является очень ненадёжным, что относится как к пакетам зашифрованного типа так и к открытым C1\C2. Причём на большинстве приватных серверов, стоят стандартные ключи, которые может заполучить любой желающий полным комплектом. Очевидно, что на такой плодотворной для читерства почве не могло не вырасти пару крутых читов, что и случилось. Вот только чтобы перехватывать трафик и модифицировать его, читы должны были уметь расшифровывать и шифровать трафик серверными ключами. Но поскольку генераторов ключей на то время не было, было вполне достаточно использовать в читах стандартную связку ключей. Сейчас же стало популярно заменять стандартные ключи шифрования на уникальные, однако так ли надёжно это решение?
Дело в том, что сам алгоритм шифрования в MuOnline является не слишком криптостойким. Он блочный, но точный алгоритм назвать не могу. На входе поступает 64 битный блок данных, он разбивается на 16 битные значения, которые шифруются 32 битными ключами(значение некоторых не превышают 16 бит), на выходе получается чуть больше данных (добавляется чексум). Давайте взглянем на пару ключей шифрования:
encode
0x0001f44f 0x00028386 0x0001125b 0x0001a192 modulus
0x00005bc1 0x00002e87 0x00004d68 0x0000354f enc_key
0x0000bd1d 0x0000b455 0x00003b43 0x00009239 xor_key
decode
0x0001f44f 0x00028386 0x0001125b 0x0001a192 modulus
0x00007b38 0x000007ff 0x0000deb3 0x000027c7 dec_key
0x0000bd1d 0x0000b455 0x00003b43 0x00009239 xor_key
Как видно диапазон modulus и xor_key в обоих ключах одинаковы, так и должно быть. Весь секрет шифрования на самом деле находится в закрытом ключе dec_key, который будет использоваться для расшифровки. Когда данные шифруются и расшифровываются в каждой итерации применяются именно 32 битные ключи(mod, xor, enc\dec). То есть сначала первым столбиком, потом вторым столбиком и т.д. Наша задача зная modulus, enc_key, xor_key получить dec_key состоящий из четырех 32 битных значений.
Чтобы произвести атаку на ключи, сначала нужно понять на каком свойстве они базируется. В нашем случае не трудно заметить что всё держится на простейшей модульной арифметике:
for ( int i=0;i<4;i++) {//encode
dwEncBuffer[i] = (
(this->m_dwXORKey[i] ^ ((WORD*)lpEncSource)[i] ^ dwEncValue) * this->m_dwEncryptionKey[i]) % this->m_dwModulus[i];
dwEncValue=dwEncBuffer[i]&0xFFFF;
}
for (int i=0;i<4;i++) {//decode
Temp1 = (( this->m_dwDecryptionKey[i] * (dwDecBuffer[i])) % ( this->m_dwModulus[i])) ^ this->m_dwXORKey[i]^Temp;
Temp = dwDecBuffer[i]&0xFFFF;
((WORD*)lpDecDest)[i] = (Temp1);
}
Это основная часть алгоритма шифрования, всё остальное никак не связано с enc\dec ключами. Немного упростим задачу отбросив излишки и переведём всё в псевдо-представление:
plaintext * enc % mod = ciphertext
ciphertext * dec % mod = plaintext
Как видно в основе всего лежит примитивное свойство модульной арифметики. Обратимся к математической формуле нахождения обратных величин по модулю:
a * x (mod n) = 1
В нашем случае в роли a выступает один из enc\dec ключей, в роли x неизвестный второй enc\dec ключ, а n это ключ mod. Так же согласно условию ключи enc, dec являются взаимно простыми с ключом mod. Таким образом зная a и n мы можем подобрать обратное число x простым перебором.
Окей давайте попробуем брутфорс. Но как насчёт диапазона перебора? Исходя из свойств модуля, каждый из enc\dec ключей не может быть >= mod. Таким образом мы сильно сокращаем диапазон поиска. Но есть ещё одна особенность алгоритма шифрования, значение ключей enc\dec не может быть больше 16 бит, из-за того что операции производятся с 16 битными значениями. Получаем что максимальный диапазон перебор не превышает отметки 0xFFFF :)
Я накидал небольшой код, демонстрирующий технику обращения ключей. Причём перебор выполняется мгновенно, таким образом подкинем ещё одну палку в колёса защищенности MuOnline. Увы, но очевидно что без сторонней защиты стандартные средства протекции игры просто бесполезны.
Прилагаю рабочий код https://github.com/JKornev/MuEncDec-Brutforcer
Всем спасибо за внимание.
Чтобы ввести в курс дела немного опишу сам механизм шифрования сетевого трафика в MuOnline. Для передачи данных используются 2 вида пакетов, это открытые 0xC1, 0xC2 и зашифрованные 0xC3, 0xC4. Для шифрования же используется алгоритм на базе открытых\закрытых ключей. Клиент содержит открытый ключ enc1.dat и закрытый dec2.dat, сервер так же содержит вторую пару: открытый enc2.dat и закрытый dec1.dat. Банальная криптосистема с открытым ключом, которая встречается на версиях с 1 сезона и до MuEx, если я не ошибаюсь.
Шифрование ложится только на критические пакеты. Однако сам протокол игры является очень ненадёжным, что относится как к пакетам зашифрованного типа так и к открытым C1\C2. Причём на большинстве приватных серверов, стоят стандартные ключи, которые может заполучить любой желающий полным комплектом. Очевидно, что на такой плодотворной для читерства почве не могло не вырасти пару крутых читов, что и случилось. Вот только чтобы перехватывать трафик и модифицировать его, читы должны были уметь расшифровывать и шифровать трафик серверными ключами. Но поскольку генераторов ключей на то время не было, было вполне достаточно использовать в читах стандартную связку ключей. Сейчас же стало популярно заменять стандартные ключи шифрования на уникальные, однако так ли надёжно это решение?
Дело в том, что сам алгоритм шифрования в MuOnline является не слишком криптостойким. Он блочный, но точный алгоритм назвать не могу. На входе поступает 64 битный блок данных, он разбивается на 16 битные значения, которые шифруются 32 битными ключами(значение некоторых не превышают 16 бит), на выходе получается чуть больше данных (добавляется чексум). Давайте взглянем на пару ключей шифрования:
encode
0x0001f44f 0x00028386 0x0001125b 0x0001a192 modulus
0x00005bc1 0x00002e87 0x00004d68 0x0000354f enc_key
0x0000bd1d 0x0000b455 0x00003b43 0x00009239 xor_key
decode
0x0001f44f 0x00028386 0x0001125b 0x0001a192 modulus
0x00007b38 0x000007ff 0x0000deb3 0x000027c7 dec_key
0x0000bd1d 0x0000b455 0x00003b43 0x00009239 xor_key
Как видно диапазон modulus и xor_key в обоих ключах одинаковы, так и должно быть. Весь секрет шифрования на самом деле находится в закрытом ключе dec_key, который будет использоваться для расшифровки. Когда данные шифруются и расшифровываются в каждой итерации применяются именно 32 битные ключи(mod, xor, enc\dec). То есть сначала первым столбиком, потом вторым столбиком и т.д. Наша задача зная modulus, enc_key, xor_key получить dec_key состоящий из четырех 32 битных значений.
Чтобы произвести атаку на ключи, сначала нужно понять на каком свойстве они базируется. В нашем случае не трудно заметить что всё держится на простейшей модульной арифметике:
for ( int i=0;i<4;i++) {//encode
dwEncBuffer[i] = (
(this->m_dwXORKey[i] ^ ((WORD*)lpEncSource)[i] ^ dwEncValue) * this->m_dwEncryptionKey[i]) % this->m_dwModulus[i];
dwEncValue=dwEncBuffer[i]&0xFFFF;
}
for (int i=0;i<4;i++) {//decode
Temp1 = (( this->m_dwDecryptionKey[i] * (dwDecBuffer[i])) % ( this->m_dwModulus[i])) ^ this->m_dwXORKey[i]^Temp;
Temp = dwDecBuffer[i]&0xFFFF;
((WORD*)lpDecDest)[i] = (Temp1);
}
Это основная часть алгоритма шифрования, всё остальное никак не связано с enc\dec ключами. Немного упростим задачу отбросив излишки и переведём всё в псевдо-представление:
plaintext * enc % mod = ciphertext
ciphertext * dec % mod = plaintext
Как видно в основе всего лежит примитивное свойство модульной арифметики. Обратимся к математической формуле нахождения обратных величин по модулю:
a * x (mod n) = 1
В нашем случае в роли a выступает один из enc\dec ключей, в роли x неизвестный второй enc\dec ключ, а n это ключ mod. Так же согласно условию ключи enc, dec являются взаимно простыми с ключом mod. Таким образом зная a и n мы можем подобрать обратное число x простым перебором.
Окей давайте попробуем брутфорс. Но как насчёт диапазона перебора? Исходя из свойств модуля, каждый из enc\dec ключей не может быть >= mod. Таким образом мы сильно сокращаем диапазон поиска. Но есть ещё одна особенность алгоритма шифрования, значение ключей enc\dec не может быть больше 16 бит, из-за того что операции производятся с 16 битными значениями. Получаем что максимальный диапазон перебор не превышает отметки 0xFFFF :)
Я накидал небольшой код, демонстрирующий технику обращения ключей. Причём перебор выполняется мгновенно, таким образом подкинем ещё одну палку в колёса защищенности MuOnline. Увы, но очевидно что без сторонней защиты стандартные средства протекции игры просто бесполезны.
Прилагаю рабочий код https://github.com/JKornev/MuEncDec-Brutforcer
Всем спасибо за внимание.
Комментариев нет:
Отправить комментарий