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 |
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::DisplayInvalidSelectedDeviceMessage
在 a2 == 2
或 a2 == 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 |
后来分析得出此处返回值是 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 |
并将被覆盖的指令剩余部分用 int3
填充。
Patch 后运行,限制已经解除。
0x3 测试并修复问题
继续下一步,开始创建,然而刚刚开了个头就出现错误了。
尝试选择企业版镜像仍然出错,看来是别的问题。
从对话框的样式可以看出是 TaskDialog,于是在 x64dbg 中对 comctl32.dll
的 TaskDialog
和 TaskDialogIndirect
设置断点,重新运行。
中断后查看调用堆栈,发现是 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 后运行,已经不会再提示错误了,顺利完成创建。