Chromium内核浏览器首页保护逻辑


发布于 2020-09-02


以前Chromium内核浏览器首页的设置是明文存在Preferences文件中的,及其不安全。很多三方程序通过改这个配置文件就可以篡改劫持浏览器的首页了。从某个版本开始,Chromium通过对设置首页的url进行hash,然后读取配置的时候对比hash的值,来判断设置首页的url是否被篡改。如果检测到被篡改,则首页默认打开新标签页,并打开设置页的时候提示用户“部分设置已经重置”,如下图所示:

settings reset

下面介绍一下chromium内核浏览器首页保护逻辑。

Chromium内核浏览器把首页的配置保存在Preferences或者Secure Preferences文件中。一般用户的首页设置都存在Secure Preferences文件中。但是如果电脑被外部管理,比如说入了域,则保存在Preferences文件中。判断电脑是否入域是通过IsOS(OS_DOMAINMEMBER)来判断的。

不管存在哪个文件中,在json配置文件的位置不是不变的:

  • session.restore_on_startup,明文,打开首页的方式,integer值
  • 值为1是恢复上次浏览器会话打开的url
  • 值为4是打开session.startup_urls设置里的urls
  • 值为5是打开新标签页
  • session.startup_urls,是打开特定网页或一组网页的urls
  • protection.macs.session.restore_on_startup,是对session.restore_on_startup值的hash结果
  • protection.macs.session.startup_urls,是对session.startup_urls值的hash结果

具体的hash算法是,先取得当前电脑的sid,获得代码如下:

MachineIdStatus GetDeterministicMachineSpecificId(std::string* machine_id) {
  DCHECK(machine_id);

  wchar_t computer_name[MAX_COMPUTERNAME_LENGTH + 1] = {};
  DWORD computer_name_size = base::size(computer_name);

  if (!::GetComputerNameW(computer_name, &computer_name_size))
    return MachineIdStatus::FAILURE;

  DWORD sid_size = SECURITY_MAX_SID_SIZE;
  char sid_buffer[SECURITY_MAX_SID_SIZE];
  SID* sid = reinterpret_cast<SID*>(sid_buffer);
  DWORD domain_size = 128;  // Will expand below if needed.
  std::unique_ptr<wchar_t[]> domain_buffer(new wchar_t[domain_size]);
  SID_NAME_USE sid_name_use;

  // Although the fifth argument to |LookupAccountNameW()|,
  // |ReferencedDomainName|, is annotated as |_Out_opt_|, if a null
  // value is passed in, zero is returned and |GetLastError()| will
  // return |ERROR_INSUFFICIENT_BUFFER| (assuming that nothing else went
  // wrong). In order to ensure that the call to |LookupAccountNameW()|
  // has succeeded, it is necessary to include the following logic and
  // obtain the domain name.
  if (!::LookupAccountNameW(nullptr, computer_name, sid, &sid_size,
                            domain_buffer.get(), &domain_size, &sid_name_use)) {
    // If the initial size of |domain_buffer| was too small, the
    // required size is now found in |domain_size|. Resize and try
    // again.
    if (::GetLastError() != ERROR_INSUFFICIENT_BUFFER)
      return MachineIdStatus::FAILURE;

    domain_buffer.reset(new wchar_t[domain_size]);
    if (!::LookupAccountNameW(nullptr, computer_name, sid, &sid_size,
                              domain_buffer.get(), &domain_size,
                              &sid_name_use)) {
      return MachineIdStatus::FAILURE;
    }
  }

  // Ensure that the correct type of SID was obtained. The
  // |LookupAccountNameW()| function seems to always return
  // |SidTypeDomain| instead of |SidTypeComputer| when the computer name
  // is passed in as its second argument and therefore both enum values
  // will be considered acceptable. If the computer name and user name
  // coincide, |LookupAccountNameW()| seems to always return the machine
  // SID and set the returned enum to |SidTypeDomain|.
  DCHECK(sid_name_use == SID_NAME_USE::SidTypeComputer ||
         sid_name_use == SID_NAME_USE::SidTypeDomain);

  char* sid_string = nullptr;
  if (!::ConvertSidToStringSidA(sid, &sid_string))
    return MachineIdStatus::FAILURE;

  *machine_id = sid_string;
  ::LocalFree(sid_string);

  return MachineIdStatus::SUCCESS;
}

这个sid在每台电脑上都不一样,比如我本机的sid是S-1-5-21-180806151-1063684393-2331670580。后续会使用这个sid去拼接要保护的数据。这样即使同样的设置数据,实际hash的数据每台电脑上都不一样,提高了安全性。

比如要保护session.startup_urls的配置的url,会被拼接成一个S-1-5-21-180806151-1063684393-2331670580session.startup_urls["https://www.baidu.com/"]的字符串,然后对这个字符串进行hmac SHA256 哈希。

hmac需要一个key,Chromium内核这个key的值为空,Chrome浏览器这个值如下:

  uint8_t k[64] = {0xe7, 0x48, 0xf3, 0x36, 0xd8, 0x5e, 0xa5, 0xf9, 0xdc, 0xdf,
                   0x25, 0xd8, 0xf3, 0x47, 0xa6, 0x5b, 0x4c, 0xdf, 0x66, 0x76,
                   0x00, 0xf0, 0x2d, 0xf6, 0x72, 0x4a, 0x2a, 0xf1, 0x8a, 0x21,
                   0x2d, 0x26, 0xb7, 0x88, 0xa2, 0x50, 0x86, 0x91, 0x0c, 0xf3,
                   0xa9, 0x03, 0x13, 0x69, 0x68, 0x71, 0xf3, 0xdc, 0x05, 0x82,
                   0x37, 0x30, 0xc9, 0x1d, 0xf8, 0xba, 0x5c, 0x4f, 0xd9, 0xc8,
                   0x84, 0xb5, 0x05, 0xa8};

得到的结果是F4471F81BC6DD2D8BC854436188A805A29FAEBF31008446FAF8C9877861BB4A5

同样的,session.restore_on_startup的配置页需要去哈希,然后更新上。