Chrome Hook 去除圆角


Chrome Hook 去除圆角

起因

Chrome 在 Windows 11 下的右键菜单和系统菜单会自动启用圆角,由于我使用了 Win11DisableOrRestoreRoundedCorners

,所以这个和系统的又不匹配(Chrome 的菜单不经由 DWM 渲染),所以得另寻他法。

Chromium 代码

自从 https://chromium-review.googlesource.com/c/chromium/src/+/3176190 起, Chromium 监测到 Windows 版本 >= 11 时就会自动开启圆角, 基于 Chromium 内核的浏览器, CEF 与 Electron App 也是如此。在以前可以通过附加参数或 chrome://flags 进行关闭,但是这个功能取消了测试被正式合并,变成了默认开启状态。

void MenuConfig::Init() {
  // ...
    
  win11_style_menus = base::win::GetVersion() >= base::win::Version::WIN11;
  UMA_HISTOGRAM_BOOLEAN("Windows.Menu.Win11Style", win11_style_menus);
  separator_upper_height = 5;
  separator_lower_height = 7;

  if (win11_style_menus)
    corner_radius = 8;
}

可见,只要满足 base::win::GetVersion() >= base::win::Version::WIN11 就会开启圆角。

base::win::GetVersion() 返回的定义在 base/win/windows_version.cc 中,

// static
OSInfo** OSInfo::GetInstanceStorage() {
  // Note: we don't use the Singleton class because it depends on AtExitManager,
  // and it's convenient for other modules to use this class without it.
  static OSInfo* info = []() {
    _OSVERSIONINFOEXW version_info = {sizeof(version_info)};

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
    // GetVersionEx() is deprecated, and the suggested replacement are
    // the IsWindows*OrGreater() functions in VersionHelpers.h. We can't
    // use that because:
    // - For Windows 10, there's IsWindows10OrGreater(), but nothing more
    //   granular. We need to be able to detect different Windows 10 releases
    //   since they sometimes change behavior in ways that matter.
    // - There is no IsWindows11OrGreater() function yet.
    ::GetVersionEx(reinterpret_cast<_OSVERSIONINFOW*>(&version_info));
#pragma clang diagnostic pop

    DWORD os_type = 0;
    ::GetProductInfo(version_info.dwMajorVersion, version_info.dwMinorVersion,
                     0, 0, &os_type);

    return new OSInfo(version_info, GetSystemInfoStorage(), os_type);
  }();

  return &info;
}

注意代码与注释,这里使用了一个在 Windows 8 后被 deprecated 的函数 (GetVersionEx),在 Unicode 环境下,被宏定义为 GetVersionExW

https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getversionexw

参数

[in, out] lpVersionInformation

接收操作系统信息的 OSVERSIONINFOOSVERSIONINFOEX 结构。

在调用 GetVersionEx 函数之前,请根据需要设置结构的 dwOSVersionInfoSize 成员,以指示要传递给此函数的数据结构。

返回值

如果函数成功,则返回值为非零值。

如果函数失败,则返回值为零。 要获得更多的错误信息,请调用 GetLastError。 如果为 OSVERSIONINFOOSVERSIONINFOEX 结构的 dwOSVersionInfoSize 成员指定无效值,则函数将失败。

这里我想 Hook 这个 API,让它返回一个 Windows 10 的版本信息。事实上我们只要修改版本结构体的成员即可。

typedef struct _OSVERSIONINFOEXA {
  DWORD dwOSVersionInfoSize;
  DWORD dwMajorVersion;
  DWORD dwMinorVersion;
  DWORD dwBuildNumber;
  DWORD dwPlatformId;
  CHAR  szCSDVersion[128];
  WORD  wServicePackMajor;
  WORD  wServicePackMinor;
  WORD  wSuiteMask;
  BYTE  wProductType;
  BYTE  wReserved;
} OSVERSIONINFOEXA, *POSVERSIONINFOEXA, *LPOSVERSIONINFOEXA;
dwMajorVersion

操作系统的主要版本号。

dwMinorVersion

操作系统的次要版本号。

dwBuildNumber

操作系统的内部版本号。

这三个是我们需要的。

Hook

这里我使用了 Detours 库。Microsoft Detours 是一个开源库,用于在Microsoft Windows上拦截,监视和检测二进制函数。最常用于在Windows应用程序中拦截 Win32 API 调用。可以添加调试工具并将任意DLL附加到任何现有的 Win32 二进制文件。

https://github.com/microsoft/Detours

这里为了方便调试,我写了一个 dummy client,先对它进行 API 劫持。

Dummy Client

#pragma warning(disable : 4996 // GetVersionExW() 是一个 deprecated 函数
#define UNICODE                // GetVersionEx() 在 define 了 UNICODE 之后才是 GetVersionExW()
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>

int __cdecl main()
{
	OSVERSIONINFO osvi;
    ZeroMemory(&osvi, sizeof(OSVERSIONINFO));
    osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
    GetVersionExW(&osvi);
    printf("Windows Version: %ld.%ld.%ld\n", osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.dwBuildNumber);
    return 0;
}

这里直接编译运行会提示 Windows NT 6.2 (Windows 8).

https://stackoverflow.com/questions/58294262/getversionex-windows-10-detected-as-windows-8

mt /manifest GetVersion.manifest -outputresource:$(BIND)\GetVersion.exe

GetVersion.manifest

<!-- GetVersion.exe.manifest -->
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
    <assemblyIdentity
        type="win32"
        name="Contoso.ExampleApplication.ExampleBinary"
        version="1.0.0.0"
        processorArchitecture="x86"
    />
    <description>Contoso Example Application</description>
    <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
        <application>
            <!-- Windows 10 and Windows 11 -->
            <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
            <!-- Windows 8.1 -->
            <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
            <!-- Windows 8 -->
            <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
            <!-- Windows 7 -->
            <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
            <!-- Windows Vista -->
            <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
        </application>
    </compatibility>
    <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
        <security>
            <requestedPrivileges>
                <!--
                  UAC settings:
                  - app should run at same integrity level as calling process
                  - app does not need to manipulate windows belonging to
                    higher-integrity-level processes
                  -->
                <requestedExecutionLevel
                    level="asInvoker"
                    uiAccess="false"
                />
            </requestedPrivileges>
        </security>
    </trustInfo>
</assembly>

注意 supportedOS

Hook

static BOOL (WINAPI * TrueGetVersionEx)(OSVERSIONINFO *osvi) = GetVersionEx;

BOOL WINAPI W10GetVersionEx(OSVERSIONINFO *osvi)
{
    BOOL ret = TrueGetVersionEx(osvi);
	if (ret) {
		osvi->dwMajorVersion = 10;
		osvi->dwMinorVersion = 0;
		osvi->dwBuildNumber  = 19045;
	}
	printf("Called W10GetVersionEx().\n");
    return ret;
}

注入

但 Chrome 貌似用绝对路径加载,自己的dll还有签名。所以 DLL 劫持路子暂时走不通。

这里使用 Detour 的 withdll,或者改 IAT。

参考

Chromium 代码

DLL 劫持

PoC 里无法劫持的 UBR (Update Build Revision) 版本号

setdll.exe 原理 - IAT

使 withdll.exe 后台运行


文章作者: sfc9982
版权声明: 本博客所有文章除特別声明外,均采用 CC BY-NC-ND 4.0 许可协议。转载请注明来源 sfc9982 !
  目录