Добрый день, меня зовут Богдан мой профиль - 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. этот анализ мы проводили с товарищем Никитой, Никита тебе респект, славная была охота, в одного возможно и не расковырял бы так быстро!
