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

Комментарии (0)