Google Chrome и Secure Preferences

chrome_art

На нашем форуме и не только, с некоторой периодичностью люди интересуются алгоритмом генерации "защитного кода" в файле Secure Preferences для браузера Google Chrome.
Зачем он браузеру? Этот код используется для проверки целостности настроек расширений и некоторых других параметров, проще говоря - HMAC. Зачем он людям? Вероятно, это необходимый этап для тихой установки расширений или изменения настроек браузера. Давайте разберемся, где и как происходит генерация этих HMAC'ов.

Для начала заглянем в исходный код проекта Chromium. Беглый поиск по слову hash вывел меня на файл pref_hash_calculator.cc. И, что характерно, именно здесь все и происходит. Процитирую код основных методов, которые отвечают за генерацию:

Авторское форматирование было задвинуто подальше, так как у меня личная неприязнь к такому стилю. Код выглядит довольно простым, осталось понять, что вообще подается на вход. Чтобы не скачивать весь исходный код Chromium и не собирать его, поступим следующим образом: поставим официальный Chrome, скачаем один лишь файл pref_hash_calculator.cc и укажем в Visual Studio путь к отладочным символам для Google Chrome:

Все готово к исследованию. Запускаем браузер, атачимся к процессу, ждем, пока прогрузятся отладочные символы (можно в настройках указать загрузку символов только для модуля chrome.dll, этого будет достаточно), открываем файл pref_hash_calculator.cc в MSVC и ставим брейкпоинт на методе Calculate. Теперь нам необходимо совершить какое-нибудь действие, которое приведет к вычислению хэша, например, установить произвольное расширение из Chrome Web Store. Устанавливаем и попадем на наш брейкопинт.

breakpoint1

Мы видим значения seed_ (перевел в hex для удобства):

И device_id:

Причем seed_ - постоянная величина (но, скорее всего, может меняться от версии к версии), а device_id - уникальный идентификатор компьютера. Откуда берется seed_? Не вдаваясь в подробности поиска, скажу, что он содержится в файле resources.pak, который находится в директории с браузером. Формат содержимого файла известный и давно описан, например, тут. Давайте попробуем самостоятельно извлечь seed_ из resources.pak. Для этого я напишу простенький скрипт на Perl:

Делаем тестовый прогон и узнаем, что seed_ содержится в ресурсе с ID 609, который скорее всего тоже меняется.

script

Теперь нам необходимо получить device_id. Откуда его берет Chrome? Не буду вас утомлять отладчиком, просто скажу, что нас интересует функция GetMachineId из файла machine_id.cc. Приведу ее исходный код на всякий случай:

Этот код является частью сторонней библиотеки RLZ. Я не стал особо копаться в логике вызовов, а просто выдрал код, немного подредактировал и сделал из него отдельный файл, который можно смело собирать и тестировать под Windows (генерация device_id отличается в зависимости от ОС). Ссылка на проект для Microsoft Visual Studio 2013 в конце статьи.
Итак, у нас есть seed_, device_id, осталось обратить свое внимание на два оставшихся аргумента, которые передаются в GetMessage - это path и value. Зайдем сразу внутрь функции GetMessage и посмотрим, что она формирует.

breakpoint2

Мы видим, что path содержит путь к настройкам расширения в Secure Preferences:

А value (value_as_string) - настройки расширения в JSON:

Подытожим логику вычисления:

В качестве hmac используется HMAC SHA256, это видно по коду. Приведу пример простого скрипта, который парсит расширения из файла Secure Preferences и вычисляет hmac для каждого из них, а также super_mac. Алгоритм вычисления прост:

super_mac используется для проверки целостности некоторых настроек браузера и массива пар ид_расширения - hmac. Наконец-таки скрипт:

Запустим скрипт, указав в качестве аргументов device_id, seed и путь к файлу Secure Preferences:

script2

Как мы видим, скрипт успешно отработал и корректно вычислил HMAC'и.

Скрипты из статьи и проект для MSVC 2013, вычисляющий device_id: скачать

Google Chrome и Secure Preferences: 38 комментариев

  1. A lil bit offtop, but who wants use Chrome, Opera(nu) & Vivaldi, etc with WebRTC, that cannot be disabled.

    vasya_voin: Otherwise - u can always filter FF quotes, and other stuff with "Privoxy", check rdot forum topic for this.

        1. Зачем компилировать? Я же писал, что достаточно отладочные символы подключить и интересующие одиночные файлы скачать

  2. Кстати, для Яндекс-браузера device_id не нужен. Сообщение формируется extension_path + extensio_setting. А в качестве key можно передать 0x00

  3. Privet Kaimi, iskal infu nas4iot faila Secure Preferences i natknulsia na tvoi sait(o4eni interesnii kstati, molodez), doljen skazati cho ti edinstvenii isto4nik takoi poleznoi informazii (poleznoi dlia griaznih del pravda :D).
    Ia doljen sdelati installer kotorii v konze dobavliaet stranizu nashei kompanii v startup_pages v chrome. Vsio kak po maslu no na samom glavnom:

    c:\Users\operatore 3\AppData\Local\Google\Chrome\User Data\Default>hmac.pl 837552B31736B91B48643763FE6E1F494FC1D936AE09173C3D E748F336D85EA5F9DCDF25D8F347A65B4CDF667600F02DF6724A2AF18A212D26B788A25086910CF3A90313696871F3DC05823730C91DF8BA5C4FD9C884B505A8
    "Secure Preferences"

    values on reference is experimental at C:\Users\operatore 3\AppData\Local\Google\Chrome\User Data\Default\hmac.pl line 91.
    Can't use an undefined value as a HASH reference at C:\Users\operatore 3\AppData\Local\Google\Chrome\User Data\Default\hmac.pl line 36.

    K sojalenii ia v Perle ne 4eshu, i ia eshio doljen perevesti eto vsio na C#... Smojesh proveriti po4emu tvoi script u menia ne rabotaet (Ia kru4u ego na Perl 5.22.0 Strawberry)? a esli esti jelanie i vremia to podmodifizirovati ego 4tob kak parametri polu4al: "device_id, seed, key, value" e vidoval hmac dlia "value" i novii super-mac (vsio na stdout). Potom ia ego sobiru v exe, sunu v installer i budu parsiti ego stdout kogda nado.

    Ogromnoe spasibo za takuiu infu, i sait krasava :)

    Privet iz Italii

    1. Izvini, script rabotaet otli4no, prosto ia emu podoval osobii file "Secure preferences", neznaiu po4emu no na moiom pk tam nahoditsia tolko super-mac i vesit 105byte, daje s novoi ustanovkoi, a na drugih pk 30-50kb i oni rabotaiut na tvoiom scripte.

  4. Izviniaiusi, po4ital script paru raz i vsio ponial, Perl menia pugal a v konze konzov vsio o4eni prosto, u menia on ne rabotal potomu4to ia imel levii "Secure Preferences" file, tam tolko super-mac nohoditsia, a ostalnie mac v "Preferences".

  5. Скрипт, запущенный в версиях Perl под Windows (ActivePerl 5.20.? x64, Strawberry 5.22.0 x32) каждый раз возвращают разные MAC для расширений: как собственно посчитанные, так и ref (

    SuperMac всегда считается верно

    1. Не наблюдаю такой проблемы с Strawberry Perl 5.22 x64.
      Правда выдает warning на строку if(scalar values $ref->{$key} == 0) , но считает корректно.

      Другое дело список расширений каждый раз в произвольном порядке выдается, но это ни на что не влияет.

      1. > Другое дело список расширений каждый раз в произвольном порядке выдается, но это ни на что не влияет.
        Ааа, точно, в произвольном порядке.

        Спасибо.

  6. Здравствуйте.
    Я получил $seed с помощью скрипта и $device_id с помощью проекта rlz_id. Вместо аргументов командной строки в скрипте вычисления hmac указал:
    my $sec_pref_file = 'C:\\Users\\...\\Default\\Secure Preferences';
    my $seed = 'e748f33...505a8';
    my $device_id = '6A7411E...290539F';
    При запуске получаю такие ошибки:
    values on reference is experimental at startpage.pl line 90.
    Global symbol "$device_id" requires explicit package name (did you forget to declare "my $device_id"?) at startpage.pl line 48.
    Global symbol "$key" ... line 50.
    Global symbol "$device_id" ... line 66.
    Global symbol "$key" ... line 68.

    Строки кода:
    48 while(my $ref = shift @queue)
    50 next unless ref $ref eq ref {};
    66 {
    68 delete $ref->{$key} ;

    Среда:
    Window 10 x64
    perl v5.22.1 built for MSWin32-x86-multi-thread-64int
    Binary build 2201 [299574] provided by ActiveState http://www.ActiveState.com
    Built Dec 24 2015 12:36:28

    Подобные ошибки perl выдает если переменные не инициализированы в режиме strict, однако они инициализированы! не понимаю в чем причина.
    В perl я новичок, установил только ради Вашего скрипта

    Помогите устранить указанные ошибки компиляции, пожалуйста :)

  7. спасибо. заработало. я запускал и редактировал разные файлы :(
    Существует ли один скрипт, выполняющий все этапы вычисления hmac включая определение входных параметров?

  8. Kaimi, большое спасибо за пост, очень познавательно. Есть вопрос..я в MSVC 2012 Ultimate указал путь к серверу с отладочными символами и при подключении к процессу chrome.exe, идет их подгрузка, но когда я открываю файл pref_hash_calculator.cc и ставлю бряк, появляется мессага о том, что символы для данного элемента не подгружены и остановки не будет?.

    1. Может символы не подгрузились (можно посмотреть в логе студии), также не вижу pref_hash_calculator в исходниках по старому пути (видимо куда-то перенесли), может не к тому процессу хрома подключился, может еще что-то...

  9. Hello,
    When I try to get the HMAC from your example in C++ or Python I have the same hash, but is different than yours. What is wrong? (Please see below)

    import hmac
    import hashlib
    import base64
    import binascii

    device_id = "7CD6D9C7354E9165A3A4CBE097021669F4C026E7AA578B0CA9";
    key = "e748f336d85ea5f9dcdf25d8f347a65b4cdf667600f02df6724a2af18a212d26b788a25086910cf3a90313696871f3dc05823730c91df8ba5c4fd9c884b505a8";
    content = '{"ack_external":true,"app_launcher_ordinal":"zs","creation_flags":137,"from_bookmark":false,"from_webstore":true,"initial_keybindings_set":true,"install_time":"13074357069686027","lastpingday":"13074332401422358","location":1,"manifest":{"api_console_project_id":"889782162350","app":{"launch":{"local_path":"main.html"}},"container":"GOOGLE_DRIVE","current_locale":"en_US","default_locale":"en_US","description":"Create and edit presentations ","icons":{"128":"icon_128.png","16":"icon_16.png"},"key":"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDLOGW2Hoztw8m2z6SmCjm7y4Oe2o6aRqO+niYKCXhZab572by7acqFIFF0On3e3a967SwNijsTx2n+7Mt3KqWzEKtnwUZqzHYSsdZZK64vWIHIduawP0EICWRMf2RGIBEdDC6I1zErtcDiSrJWeRlnb0DHWXDXlt1YseM7RiON9wIDAQAB","manifest_version":2,"name":"Google Slides","offline_enabled":true,"update_url":"https://clients2.google.com/service/update2/crx","version":"0.9"},"page_ordinal":"n","path":"aapocclcgogkmnckokdopfmhonfmgoek\\0.9_0","state":1,"was_installed_by_default":true,"was_installed_by_oem":false}'
    path = "extensions.settings.aapocclcgogkmnckokdopfmhonfmgoek";
    msg = device_id + path + content;

    dig = hmac.new(binascii.unhexlify(key), msg=msg, digestmod=hashlib.sha256).digest()
    hmac_res = base64.b64encode(dig).decode() # py3k-mode
    print binascii.hexlify(dig)

    Results:
    24664baa780f87978734adc88e531d3e50e2daa230401e92ef718d3b9de07169

      1. It's not about Chrome, it's all hardcoded right now with the values of your example. The fact is that my HMAC method doesn't return the same hash as you, so I'm wondering where my mistake is.
        I'm trying to validating the algorithm before actually working on real data.

          1. I understand your statement, however I'm just playing with the data (already normalized) you've put in your demonstration. The blocking point is the very last part, where we calculate the HMAC. I'm sure there's something, non-obvious, missing in the explanations...

          2. Ok, I will answer to myself. I have been able to get the correct value on Python, by fixing several things:
            - Json needs to be parsed/dumped as string (validation)
            - Json needs to PRESERVE keys order
            - I had an error in Json string, \\ needs to be \

            Here's the correct Python:

            import hmac
            import hashlib
            import base64
            import binascii
            import json
            from collections import OrderedDict

            device_id = "7CD6D9C7354E9165A3A4CBE097021669F4C026E7AA578B0CA9"
            key = "e748f336d85ea5f9dcdf25d8f347a65b4cdf667600f02df6724a2af18a212d26b788a25086910cf3a90313696871f3dc05823730c91df8ba5c4fd9c884b505a8"
            content = '{"ack_external":true,"app_launcher_ordinal":"zs","creation_flags":137,"from_bookmark":false,"from_webstore":true,"initial_keybindings_set":true,"install_time":"13074357069686027","lastpingday":"13074332401422358","location":1,"manifest":{"api_console_project_id":"889782162350","app":{"launch":{"local_path":"main.html"}},"container":"GOOGLE_DRIVE","current_locale":"en_US","default_locale":"en_US","description":"Create and edit presentations ","icons":{"128":"icon_128.png","16":"icon_16.png"},"key":"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDLOGW2Hoztw8m2z6SmCjm7y4Oe2o6aRqO+niYKCXhZab572by7acqFIFF0On3e3a967SwNijsTx2n+7Mt3KqWzEKtnwUZqzHYSsdZZK64vWIHIduawP0EICWRMf2RGIBEdDC6I1zErtcDiSrJWeRlnb0DHWXDXlt1YseM7RiON9wIDAQAB","manifest_version":2,"name":"Google Slides","offline_enabled":true,"update_url":"https://clients2.google.com/service/update2/crx","version":"0.9"},"page_ordinal":"n","path":"aapocclcgogkmnckokdopfmhonfmgoek\\\\0.9_0","state":1,"was_installed_by_default":true,"was_installed_by_oem":false}'
            path = "extensions.settings.aapocclcgogkmnckokdopfmhonfmgoek"

            try:
            content_json = json.loads(content, object_pairs_hook=OrderedDict)
            except Exception, e:
            print 'Invalid json: %s' % e
            exit(1)

            content_str = json.dumps(content_json, separators=(',', ':'))
            msg = device_id + path + content_str

            dig = hmac.new(binascii.unhexlify(key), msg=msg, digestmod=hashlib.sha256).digest()
            hmac_res = base64.b64encode(dig).decode() # py3k-mode
            print content_str + '\n'
            print binascii.hexlify(dig)

  10. Спасибо за интересную и очень полезную работу!!!
    проблему с кодом:
    Experimental values on scalar is now forbidden at hmac.pl line 91.
    я решил так:
    if(scalar values %{$ref->{$key}} == 0)

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *