【问题标题】:Is the Win32 DLGTEMPLATEEX structure returned from LoadResource meant to be user-writable?从 LoadResource 返回的 Win32 DLGTEMPLATEEX 结构是否意味着用户可写?
【发布时间】:2020-02-25 00:22:12
【问题描述】:

我正在使用一个内部 MFC 应用程序,它试图通过在使用 Win32 LoadResource 函数获得的 DLGTEMPLATEEX 结构中找到对话框并直接写入该结构的字体大小成员来以编程方式更改对话框的字体大小。在 VS 2019 的 Debugger 下运行时,会触发内存写访问冲突。

BOOL CMk20Dlg::Create(UINT nIDTemplate, CWnd* pParentWnd)
{
    LPCTSTR lpszTemplateName = MAKEINTRESOURCE(nIDTemplate);

    ASSERT(IS_INTRESOURCE(lpszTemplateName) || AfxIsValidString(lpszTemplateName));

    m_lpszTemplateName = lpszTemplateName;
    if (IS_INTRESOURCE(m_lpszTemplateName) && m_nIDHelp == 0)
        m_nIDHelp = LOWORD(reinterpret_cast<DWORD_PTR>(m_lpszTemplateName));

    HINSTANCE hInst = AfxFindResourceHandle(lpszTemplateName, RT_DIALOG);
    HRSRC hResource = ::FindResource(hInst, lpszTemplateName, RT_DIALOG);
    if (hResource == NULL)
        TRACE(_T("FindResource failed with error %d\n"), ::GetLastError());

    HGLOBAL hTemplate = ::LoadResource(hInst, hResource);
    DLGTEMPLATE* lpTemplate = static_cast<DLGTEMPLATE*>(::LockResource(hTemplate));

    WORD* pwFontSize = GetResFontSizeOffset(lpTemplate);
    *pwFontSize = static_cast<WORD>((8.0 * theApp.GetFontScaleFactor()) + 0.5);
    // ^-- crash on write to *pwFontSize

我相信代码在返回的结构中找到了正确的目标内存位置,因为该结构在预期的偏移量处具有正确的签名(0xFFFF 将其标识为 DLGTEMPLATEEX 而不仅仅是 DLGTEMPLATE - 尽管代码没有任何错误检查是否不是),并且我单步执行了计算指向字体大小成员的指针的代码。它指向一个值“8”的 DWORD,这对于系统字体大小来说似乎是合理的。

WORD* CMk20Dlg::GetResFontSizeOffset(DLGTEMPLATE* lpTemplate)
{
   WORD* pwPtr = reinterpret_cast<WORD*>(lpTemplate);

    // lpTemplate is in the format of a DLGTEMPLATEEX
    // Refer Win32 SDK documentation of "struct" DLGTEMPLATEEX
    ++pwPtr;        // dlgVer
    ++pwPtr;        // signature
    /* ^-- At this point *pwPtr has value 0xFFFF in the debugger, indicating DLGTEMPLATEEX */

     /* The ++'ing of pwPtr continues through the rest of the struct members,
        then it skips over the menu array, window class array, title, etc.  Finally: */

    // We are now pointing at the font size word
    return pwPtr;
}

当代码试图通过指针写入将“8”实际更改为另一个数字时,返回值指向 DWORD“8”,它在 Visual Studio 2019 调试器中崩溃,并出现内存“写访问冲突”。

我对我在这里看到的情况有很多担忧。

首先,像这样破坏这个内存位置有什么用吗?操作系统如何看到值发生了变化?我希望正确的方法是一些 Win32 函数调用,如“SetDlgFontSize”之类的,而不是在数据结构中探索。

其次,这段代码到底是如何工作的?它是否适用于旧版本的 Windows,但不适用于 Windows 10?现在失败是因为我将项目升级到 VS 2019(以前是到 2012 年)。还是 VS 2019 调试器(或 Debug 版本)抢先在具有我不应该编写的操作系统结构的内存页面上设置写保护位?

第三,我需要决定是通过注释掉崩溃代码还是“正确”设置字体大小来解决这个问题。这取决于违规代码是否真的产生了影响。如果没有,我可以将其注释掉,但如果我需要正确复制效果,我迄今为止发现的唯一 MFC 代码可以将字体大小和字体系列设置在一起。那么我还需要添加代码来找出正确的字体...

【问题讨论】:

  • 我希望从LoadResource/LockResource 返回的值本质上是指向可执行文件/DLL 中包含的资源数据的指针,并且它是不可写的(但我没有通过快速搜索找到任何与此相关的文档)。
  • 我已经修改了返回的指针中的数据,并没有产生任何不良影响,但是我总是在调用 LoadResource() 之后使用 LockResource()。您可以查看 MFC 中 CDialogTemplate 的实现。它在 中声明。实现在 MFC 源的 dlgtempl.cpp 文件中。
  • 如果您只想更改对话框字体,请参阅此link

标签: c++ windows winapi fonts mfc


【解决方案1】:

只要有问题的对话框是在 clobber 之后创建的,这样做会更改用于创建对话框的模板是有道理的(只要内存权限允许写入),但是我希望资源内存是只读的,所以我很惊讶这在没有访问冲突的情况下工作。

它可以工作(ed),因为每次创建对话框时都会读取对话框模板。如果在执行此序列时已创建对话框,则它应该无效。

加载资源(通过 FindResource + LoadResource + LockResource)将整个对话框模板复制到显式分配的缓冲区,进行任何需要的更改然后使用 DialogBoxIndirect 或 DialogBoxIndirectParam 从内存中实例化对话框会更有意义模板(而不是来自资源 ide 标识符)。制作这样的副本可以避免内存权限问题的可能性。

我想你也可以使用 VirtualProtect 并使有问题的页面可写,但这让我觉得解决这类问题的方法很糟糕。

【讨论】:

  • Raymond Chen 的一篇博客文章,不是关于这个,而是关于 Property Sheets,其中包括声明,“当属性表管理器写入对话框模板以修复样式时,会引发第一次机会异常因为资源一开始是只读页面。” devblogs.microsoft.com/oldnewthing/?p=28493
猜你喜欢
  • 2020-07-29
  • 2018-07-12
  • 2012-10-08
  • 2014-08-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多