YSC's blog

解除“创建 Windows To Go 工作区” (pwcreator.exe) 的限制

ysc3839's Avatar 2020-11-16

  1. 1. 0x0 原始文件
  2. 2. 0x1 解除“U 盘不兼容”的限制
  3. 3. 0x2 解除企业版限制
  4. 4. 0x3 测试并修复问题

Windows 官方提供的“创建 Windows To Go 工作区” (pwcreator.exe) 有着一些限制,比如只能选择企业版 Windows,以及 U 盘不能是“可移动”的。而网上的解决方法都说要用第三方软件,但如果我不想用呢?

0x0 原始文件

因为微软从 Windows 10 2004 开始移除了 pwcreator.exe,所以要从旧版本 Windows 中提取。
以下文件是从 en_windows_10_consumer_editions_version_1909_x64_dvd_be09950e.iso 镜像中提取的,SHA256 为:

pwcreator.exe               d56a06ac1014c6d3a945caae0e722479a5712ba22afce84d99d5c47d4f656e68
pwcreator.exe.mui (en-US) aa9c80612b4845301e8b10f727fe4cbca168d9e80331fd466637817acb94a643

0x1 解除“U 盘不兼容”的限制

首先运行 pwcreator.exe,插入并选中 U 盘,看到错误提示信息:
This is a removable drive and isn't compatible with Windows To Go. Choose a drive that meets the required hardware specifications.

打算从这段错误提示下手,反向找到检查的代码。
首先要知道 Windows 不同语言的数据是存放在 .mui 文件中的,而 .mui 文件其实是只有资源数据 (Resource Data) 的 DLL。于是先用 Visual Studio (其他能查看 PE 资源数据的程序也可以) 打开 pwcreator.exe.mui。不出意料在 String Table 里面找到了这个字符串,ID 是 219

接下来用 IDA 打开 pwcreator.exe。既然是在 String Table 中,那应该会用到 LoadString 来加载。接着去 Imports 中搜索 LoadString,结果发现是空的。
那只能看看别的加载资源的 API 了,先搜索一下 FindResource,发现有 FindResourceExW。再看看引用,发现 ATL::AtlFindStringResourceInstance 等函数,可以知道该程序是使用 ATL 来加载字符串。

继续看 ATL::AtlFindStringResourceInstance 的引用,发现 ux::CDevicePage::DisplayErrorStatusMessage 这个函数,按 F5 反编译发现字符串 ID 是通过参数传进来的。

继续查看引用,发现 ux::CDevicePage::DisplayInvalidSelectedDeviceMessagea2 == 2a2 == 5 时会传递 219 给 ux::CDevicePage::DisplayErrorStatusMessage

继续查看引用,发现 ux::CDevicePage::OnDeviceListItemSelected 传递了 v4 给 ux::CDevicePage::DisplayInvalidSelectedDeviceMessage,而 v4 是一个虚函数调用的返回值。

考虑到静态查找虚函数太麻烦,记下此处函数调用所在的相对地址 192A6,用 x64dbg 打开 pwcreator.exe,在对应位置设置断点,运行,发现目标函数是 bl::CProvisionProvider::IsDiskCompatible,在 IDA 中反编译对应函数,发现一开始还是几个虚函数调用。

继续用 x64dbg 调试,发现是在上图中第三个 if 检测失败返回的,目标函数是 utils::CDiskTraits::GetMediaType,直接读取了一个结构体中的数据就返回了。

一开始我并不知道返回值的含义,于是直接改成返回 12:

mov eax, 0xC
ret

后来分析得出此处返回值是 MEDIA_TYPE,12 对应的是 FixedMedia

Patch 后运行,限制已经解除。

0x2 解除企业版限制

进入下一步,选择非企业版的镜像,看到错误提示信息:
You can only create a Windows To Go workspace with a Windows 10 Enterprise image.

与前面方法相同,找到字符串对应的 ID 是 291,然后查看 ATL::AtlFindStringResourceInstance 的引用,发现 4 处 ux::CImageSelectionPage 下的引用,但是传递进来的都不是 291。

考虑到原始字符串是 You can only create a Windows To Go workspace with a %WINDOWS_SHORT% Enterprise image.,猜测可能有个 format 的过程。又在引用中看到 utils::CBranding::Format 这个函数,于是去调查一下 utils::CBranding::Format 的引用,发现 ux::CImageSelectionPage::UpdateStatusMessage*((_DWORD *)this + 70) == 7 时传递了 291。

*((_DWORD *)this + 70) 是在别处写入的,只能先看看引用了,发现 ux::CImageSelectionPage::OnImageListItemSelected 写入了 *((_DWORD *)this + 70),并且是一个虚函数调用的返回值。

继续找到对应地址 13901 并在 x64dbg 中设置断点,发现对应函数是 bl::CProvisionProvider::IsImageCompatible,用 IDA 反编译,一下子就看到很显眼的 utils::CWindowsImageMetadata::IsEnterprise

考虑到 bl::CProvisionProvider::IsImageCompatible 还会对架构等进行检查,我只修改 utils::CWindowsImageMetadata::IsEnterprise,在 x64dbg 中将函数头部指令改成:

mov al, 0x1
ret

并将被覆盖的指令剩余部分用 int3 填充。

Patch 后运行,限制已经解除。

0x3 测试并修复问题

继续下一步,开始创建,然而刚刚开了个头就出现错误了。

尝试选择企业版镜像仍然出错,看来是别的问题。

从对话框的样式可以看出是 TaskDialog,于是在 x64dbg 中对 comctl32.dllTaskDialogTaskDialogIndirect 设置断点,重新运行。

中断后查看调用堆栈,发现是 try catch 后显示的错误,出错位置已经看不到了。

所以只能通过显示错误前抛出的异常来找到问题。重新运行并开始创建,当遇到异常中断时都记录一下调用堆栈,最后发现是 utils::CVolume::SetNoDefaultDriveLetter 抛出的异常。

继续在 IDA 中查看 utils::CVolume::SetNoDefaultDriveLetter,发现是在进行虚函数调用后检查 HRESULT 并抛出 CAtlException 异常。

继续用 x64dbg 查找目标函数,发现是 vds_ps.dll 中的 ObjectStublessClient12。依据 COM 相关知识,这是一个 Proxy/Stub DLL,估计接下来会通过 RPC 与真正的服务进行通信。所以没法解决错误,只能忽略这个错误。

返回值是 0x8004255A,对应 VDS_E_DISK_REMOVEABLE (The operation is not supported on removable media.),确实是不好解决。

找到对应的跳转位置,使用 nop 填充 (前面的 mov ebx, eax 虽然保存了错误代码,但是只在后面抛出异常时用到了 ebx,所以可以忽略)。

Patch 后运行,已经不会再提示错误了,顺利完成创建。

本文作者 : ysc3839
本文使用 CC BY-NC-SA 4.0 International 协议 进行许可。
本文链接 : https://blog.ysc3839.com/post/remove-pwcreator-restrictions.html

本文最后更新于 天前,文中所描述的信息可能已发生改变