用逆向做点正向
macOS版本的微信在2021年已经支持深色模式

Windows版本的微信到2024年还没有深色模式
看到下面链接说到:眼睛畏光的低视力人一直在反馈
期盼尽快更新Windows版本(PC版本)微信深色模式(暗色、黑暗、暗夜、黑夜模式)
试着用逆向去实现Windows版本的微信也支持深色模式
发现微信其实隐藏了深色模式,可惜不完善,修补之后效果如下

隐藏的深色模式
先试着用IDA搜素深色模式的关键词dark
发现确实有深色模式:'weui\theme\dark\theme.xml'

交叉索引一下引用字符串的地方
1 2 3 | int sub_1F35D30() sub_1F33C40(dword_32C5500, L"weui\\theme\\dark\\theme.xml"); sub_1F33C40(dword_32C54F4, L"Theme\\dark\\theme_dark.xml"); |
再看下sub_1F35D30调用的地方
dword_32C5500便是深色模式的资源路径
1 2 3 | int sub_1F35D00() if ( !dword_32C5500 ) sub_1F35D30(); return dword_32C5500; |
再看下sub_1F35D00调用的地方
可以看出byte_32B5474控制是否用深色模式
1 2 3 | int __thiscall sub_1EF62E0(_DWORD *this) if ( byte_32B5474 ) sub_1F35D00(); else sub_1F35CF0(); |
交叉索引byte_32B5474,有两个地方写入byte_32B5474
1 2 3 4 | 01EBC2ED 8A47 61 mov al, [edi+61h]01EBC2F2 A2 74542B03 mov byte_32B5474, al01EBCCE3 C605 74542B03 00 mov byte_32B5474, 0 |
在X86dbg上修改一下汇编
1 2 3 4 | 7A47C2ED | B0 01 | mov al,1 |7A47C2EF | 90 | nop |7A47CCE3 | C605 7454877B 01 | mov byte ptr ds:[7B875474],1 |
修改之后还是浅色模式
但个人名片从白色是黑色
说明深色模式有效且不完善

深色模式的原理
为了了解为什么深色模式不完善
需要梳理一下深色模式的底层原理
1、微信里面有两个主题模式
1 2 3 | 浅色:theme\default\theme.xml深色:theme\dark\theme.xml |
2、不同主题模式的颜色配置文件不同
1 2 3 4 5 6 7 | 浅色:theme\default\theme.xml<!-- color --><IncludeTheme source="weui/Theme/default/colors.xml" />深色:theme\dark\theme.xml<!-- color --><IncludeTheme source="weui/Theme/dark/colors.xml" /> |
3、同id不同主题模式下,具体值不同
1 2 3 4 5 6 7 8 9 10 11 | 浅色:theme\default\theme.xml<!-- color --><IncludeTheme source="weui/Theme/default/colors.xml" /><!-- 文字颜色 --><Color id="Text_1" opacity="1" color="#161616" /> // 浅色模式的文字比较深深色:theme\dark\theme.xml<!-- color --><IncludeTheme source="weui/Theme/dark/colors.xml" /><!-- 文字颜色 --><Color id="Text_1" opacity="1" color="#F7F7F7" /> // 深色模式的文字比较浅 |
4、界面的属性值改为从固定值变成变量值
1 2 3 | textcolor="#FF000000"改成textcolor="@color:Text_1" |
可以看出微信的底层已经有一套更换模式的基建
而深色模式之所以不完善是因为深色资源文件不完整
1 2 | theme\default\colors.xml 5150 字节theme\dark\colors.xml 4370 字节 |
修改界面的资源
如果要完善深色模式,就需要修改界面资源
先了解一下原生Duilib生成界面的流程
1、界面生成从OnCreate开始
1 2 3 4 5 6 | LRESULT WindowImplBase::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) switch (GetResourceType()) case UILIB_ZIP: m_pm.SetResourceZip(GetZIPFileName().GetData(), true); CControlUI* pRoot = builder.Create(xml, _T("xml"), this, &m_pm); |
2、builder.Create加载界面资源
1 2 3 4 5 6 7 8 9 | CControlUI* CDialogBuilder::Create(STRINGorID xml, LPCTSTR type, IDialogBuilderCallback* pCallback, CPaintManagerUI* pManager, CControlUI* pParent) if( !m_xml.Load(xml.m_lpstr) ) return NULL; if( !m_xml.LoadFromFile(xml.m_lpstr) ) return NULL; if( !m_xml.LoadFromMem((BYTE*)::LockResource(hGlobal), ::SizeofResource(dll_instence, hResource) )) return NULL; return Create(pCallback, pManager, pParent);bool CMarkup::LoadFromMem(BYTE* pByte, DWORD dwSize, int encoding) DWORD nWide = ::MultiByteToWideChar( CP_UTF8, 0, (LPCSTR)pByte, dwSize, NULL, 0 ); ::MultiByteToWideChar( CP_UTF8, 0, (LPCSTR)pByte, dwSize, m_pstrXML, nWide ); |
3、最后是解析界面资源的内容
1 2 3 4 | CControlUI* CDialogBuilder::Create(IDialogBuilderCallback* pCallback, CPaintManagerUI* pManager, CControlUI* pParent) CMarkupNode root = m_xml.GetRoot(); for( CMarkupNode node = root.GetChild() ; node.IsValid(); node = node.GetSibling() ) "linkhoverfontcolor" |
根据这个流程,就可以定位微信Duilib对应的函数
1、搜索关键词"linkhoverfontcolor"
1 2 | int __thiscall sub_1EDDBE0(_DWORD *this, int pCallback, int pManager, int pParent) "linkhoverfontcolor" |
2、再查看sub_1EDDBE0的引用
1 2 3 4 | int __thiscall sub_1EDDB80(_DWORD *this, wchar_t *Source, int a3, int a4, int a5, int a6) wcXMLData = sub_1F20210(wcXMLPath, 1); if ( wcXMLData && sub_1EE0BE0(this, *(wcXMLData + 0x2000), *(wcXMLData + 0x2004), v8) ) return sub_1EDDBE0(this, a4, a5, a6); |
3、sub_1F20210就是CMarkup::LoadFromMem
把界面资源的路径(wcXMLPath)变成界面资源的内容(wcXMLData )
1 2 3 | char __thiscall sub_1EE0BE0(WCHAR **this, LPCCH lpMultiByteStr, unsigned int cbMultiByte, int a4) v6 = MultiByteToWideChar(65001u, 0, v5, v4, 0, 0);// CP_UTF8 MultiByteToWideChar(65001u, 0, v5, cbMultiByte, v7, v6); |
用Frida的js脚本拦截修改(需要修改很多地方),就可以实现深色模式
1 2 3 4 | const targetFunctionAddress = WeChatWin.add(0x1F20210);Interceptor.attach(targetFunctionAddress,...ModifyAttribute(this.wsXMLDataArray, '', 'bkcolor', '#FF000000'); |

修改业务的代码
修改资源的属性,发现了一个问题:编辑框的文字属性修改无效
原本是是白色背景加黑色字体,现在是黑色背景加黑色字体,看不清
这就意味着:控件的属性,不是完全依赖界面资源,还需要修改业务代码
1、修改字体颜色的接口是SetAttribute
1 2 3 4 5 6 7 8 9 10 11 12 | void CRichEditUI::SetAttribute(LPCTSTR pstrName, LPCTSTR pstrValue) if( _tcscmp(pstrName, _T("textcolor")) == 0 ) if( *pstrValue == _T('#')) pstrValue = ::CharNext(pstrValue); DWORD clrColor = _tcstoul(pstrValue, &pstr, 16); SetTextColor(clrColor);void CRichEditUI::SetTextColor(DWORD dwTextColor) m_pTwh->SetColor(dwTextColor);void CTxtWinHost::SetColor(DWORD dwColor) cf.crTextColor = RGB(GetBValue(dwColor), GetGValue(dwColor), GetRValue(dwColor)); pserv->OnTxPropertyBitsChange(TXTBIT_CHARFORMATCHANGE, TXTBIT_CHARFORMATCHANGE); |
注入DLL调用SetAttribute确实能改变字体颜色
但再编辑编辑框RichEdit的文本,颜色又变成黑色
1 2 3 4 | DWORD pControl = 0x15a72bb8;const wchar_t* wcAttr = L"textcolor";const wchar_t* wcValue = L"#FFFFFFFF"; // 白色SetAttribute(pControl, wcAttr, wcValue); |
2、修改字体颜色的另一接口是SetSelectionCharFormat
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #define WM_USER 0x0400#define EM_GETCHARFORMAT (WM_USER + 58) // 0x43A#define EM_SETCHARFORMAT (WM_USER + 68) // 0x444#define SCF_SELECTION 0x0001DWORD CRichEditUI::GetSelectionCharFormat(CHARFORMAT2 &cf) const cf.cbSize = sizeof(CHARFORMAT2); TxSendMessage(EM_GETCHARFORMAT, SCF_SELECTION, (LPARAM)&cf, &lResult);bool CRichEditUI::SetSelectionCharFormat(CHARFORMAT2 &cf) cf.cbSize = sizeof(CHARFORMAT2); TxSendMessage(EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&cf, &lResult);HRESULT CRichEditUI::TxSendMessage(UINT msg, WPARAM wparam, LPARAM lparam, LRESULT *plresult) const return m_pTwh->GetTextServices()->TxSendMessage(msg, wparam, lparam, plresult); |
X86dbg对TxSendMessage下条件断点并打印参数

发现编辑文本的时候调用了GetSelectionCharFormat
1 2 | #define EM_GETCHARFORMAT (WM_USER + 58) // 0x43ACRichEditUI::TxSendMessage this=15AA51E0 msg=43A wparam=1 |
添加暂停条件:[esp+4]==0x43A
看下哪里调用GetSelectionCharFormat

发现确实调用了SetSelectionCharFormat
1 2 3 4 5 6 7 8 | #define EM_GETCHARFORMAT (WM_USER + 58) 0x43A#define EM_SETCHARFORMAT (WM_USER + 68) 0x444#define SCF_SELECTION 0x0001int __thiscall sub_1F02FA0(int *this, _DWORD *pColor, int dwBegin, int dwEnd) // GetSelectionCharFormat (*(*this + 724))(this, 0x43A, 1, pcf, v10); // SetSelectionCharFormat (*(*this + 724))(this, 0x444, 1, pcfNew, v10); |
再看下调用sub_1F02FA0的地方
发现微信在代码上直接写死成黑色
1 2 3 | void __thiscall sub_9FF580(int *this) NewColor(pColor, 0xFF000000); // 写死黑色 sub_1F02FA0(this, pColor, 0, TextLength); |
再看下调用sub_9FF580的上级函数
发现确实是有更新的时候修改字体颜色
1 2 3 4 5 6 | void __thiscall sub_01F03D70(char *this, unsigned int iNotify, int a3) // #define EN_UPDATE 0x0400 if ( iNotify == 0x400 ) //GetManager()->SendNotify(this, DUI_MSGTYPE_TEXTCHANGED); sub_1EC0390(*(this + 22), this, L"textchanged", 0, 0, this[3820]); (*(*this + 740))(this); // sub_9FF580 |
简单的绕过方法就是修改一个字节:0x400改变成0x401
1 2 3 | 7A4C3DBE | 81FB 00040000 | cmp ebx,400 |改成7A4C3DBE | 81FB 01040000 | cmp ebx,401 |
