Добрый день, меня зовут Богдан мой профиль - java-разработка, о рабочих буднях и прочих оклоджавовых штуках рассказываю в своем телеграмм канале, но сегодня не о java, ну вернее почти не о ней :)
ДИСКЛЕЙМЕР: вся информация в статье представлена с исследовательской целью и не преследует никаких деструктивных мотивов, анализ выполнен на основе открытых исходных кодов, все персонажи вымышлены, а совпадения случайны.
Пролог.
Как известно java компилируется в байт-код, скомпилированные классы также богаты мета-информацией для обеспечения механизма рефлексии что в совокупности позволяет довольно легко декомпилировать java-приложения обратно в java-код. Смотря в окошко рандомного java-приложения, строго требующего ключа активации, невольно ухмыляешься: знаю я, есть заветная строка в твоем коде, до которой добраться на самом деле не особо долго, да, есть обфускация, проверки через подпись с асимметричным шифрованием и т.д., но... ведь это только продлевает агонию и увеличивает трудозатраты вендора на не бизнесовый код, словом особенного смысла не имеет, все равно вскрывается довольно быстро. Но как обстоят дела с приложениями написанными на языках компилирующихся сразу в машинный бинарь? Спойлер.. в этой статье мы так этого и не узнаем =).
Грязные танцы.
Ну что ж, волею судеб пациентом был выбран Cockroach DB, а вернее его Enterprise часть, его мы и будем анализировать в этой статье. Из информации на офф сайте мы можем понять что таракан написан на Go и ключ активации для Cockroach DB вводится с помощью выполнения SQL запроса:
SET CLUSTER SETTING enterprise.license = 'xxxxxxxxxxxx';
Попробуем развернуть кластер БД и посмотреть применится ли Enterprise лицензия Cockroach DB по инструкции от вендора, сам процесс развертывания БД опускаю. Ожидаемо получаем ошибку:
Ну и ладно, не может же быть все так просто, зато теперь в случае успеха будет понятно что защита от рандомной последовательности символов таки есть и успех - точно успех :D.
Окей, едем дальше, на офф сайте есть ссылка на гит, интересно, давайте поищем по фразе "invalid license string", ну а вдруг... И да, находим файлик с воодушевляющим названием license_check.go, хмхмхм, ну не может же все быть так просто? Первый взгляд в Go после 6 лет онлиджава это как сквозь три слоя мутного целофана смотреть в темноту, ну да ладно, завариваем чай и ковыряем, в коде находим строки из которых можно сделать вывод что сюда приходит ключ закодированный в base64, который после декодинга в массив байт, десериализуется в объект licenseccl.License:
// decode attempts to read a base64 encoded License.
func decode(s string) (*licenseccl.License, error) {
...
...
}
// getLicense fetches the license from the given settings, using Settings.Cache
// to cache the decoded license (if any). The returned license must not be
// modified by the caller.
func getLicense(st *cluster.Settings) (*licenseccl.License, error) {
str := enterpriseLicense.Get(&st.SV)
if str == "" {
return nil, nil
}
cacheKey := licenseCacheKey(str)
if cachedLicense, ok := st.Cache.Load(cacheKey); ok {
return cachedLicense.(*licenseccl.License), nil
}
license, err := decode(str)
if err != nil {
return nil, err
}
st.Cache.Store(cacheKey, license)
return license, nil
}
Взглянем на класс licenseccl.License, интересен здесь код:
// Decode attempts to read a base64 encoded License.
func Decode(s string) (*License, error) {
if s == "" {
return nil, nil
}
if !strings.HasPrefix(s, LicensePrefix) {
return nil, errors.New("invalid license string")
}
s = strings.TrimPrefix(s, LicensePrefix)
data, err := base64.RawStdEncoding.DecodeString(s)
if err != nil {
return nil, errors.Wrapf(err, "invalid license string")
}
var lic License
if err := protoutil.Unmarshal(data, &lic); err != nil {
return nil, errors.Wrap(err, "invalid license string")
}
return &lic, nil
}
Из которого видно что в base64 закодированы байты некой сущности сериализованной с помощью protobuf, описание proto файла лежит рядом, в нем вся структура лицензии, никаких ключей, подписей, etc., только тип лицензии и модель использования, ну не может же быть все так просто?!
message License {
reserved 1;
int64 valid_until_unix_sec = 2;
enum Type {
NonCommercial = 0;
Enterprise = 1;
Evaluation = 2;
}
Type type = 3;
string organization_name = 4;
enum Usage {
option (gogoproto.goproto_enum_prefix) = false;
option (gogoproto.goproto_enum_stringer) = false;
Unspecified = 0;
Production = 1;
PreProduction = 2;
Development = 3;
}
Usage usage = 5;
}
Но наверняка где-то там, выше по стеку вызовов, вводимая пользователем лицензия расшифровывается и/или верифицируется ее подпись и этот base64 приезжает сюда уже после? Не может же быть все так просто?!! Есть только один быстрый способ проверить, пишем на java код который сериализует proto схему с нужными значениями и завернет все это добро в base64, ну а дальше выполняем заветный запрос который нас послал ранее но уже с полученным ключем:
Сначала ключ таракан не принял, но внимательно еще раз посмотрев исходники licenseccl.License обнаружил что перед декодингом base64, отрезается некий префикс "crl-0-", добавив его к сгенеренному base64 таракан стремительно прожевал лицензию даже не поперхнувшись.
Заключение.
Мораль сей басни такова, что иногда просто нужно копнуть не в глубь, а чуть рядом и обязательно упрешься в крышку сундука, ну или впрыгнешь обеими ногами в жир, тут уж как повезет =)
Код кейгена выкладывать тут не буду, но у меня в телеге в комментариях к этой статье кто-то его заново написал и уже выложил.
P.S. этот анализ мы проводили с товарищем Никитой, Никита тебе респект, славная была охота, в одного возможно и не расковырял бы так быстро!