Windows桌面水印去除工具Universal Watermark Disabler原理分析及实现
1.背景
最近做驱动开发,开启了系统测试模式,于是桌面的右下角就有一个水印,如下图:
测试了网上修改注册表方法不起作用,最后找到一款工具Universal Watermark Disabler可以把水印去除掉。于是对其原理有些兴趣,就有了相关的分析及编程实现。
2、相关分析
2.1 相关行为分析
使用Process Monitor抓取相关操作,发现可用的操作如下:
2.1.1 创建了一个名为painter_x64.dll的文件
2.1.2 修改为注册表 HKCR\CLSID\{ab0b37ec-56f6-4a0e-a8fd-7a8bf7c2da96}\InProcServer32的值
注册表相应的原值现为%SystemRoot%\system32\explorerframe.dll, 而修改后的值为如下
2.1.3 大致结论
注册表项的相关文件和桌面进程相关,应该是在桌面进程启动时加载了相应的dll文件,而该工具替换成painter_x64.dll,该dll做了相应的功能来实现水印过滤。结下来应该分析该dll的功能。
2.2 分析painter_x64.dll
直接上IDA看相关的功能。
2.2.1 主入口代码如下:
BOOL __stdcall DllEntryPoint(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved)
{
HMODULE v4; // rbx
HMODULE v5; // rax
BOOL (__stdcall *ExtTextOutW)(HDC, int, int, UINT, const RECT *, LPCWSTR, UINT, const INT *); // rax
HMODULE v7; // rax
int (__stdcall *LoadStringW)(HINSTANCE, UINT, LPWSTR, int); // rax
const CHAR *v9; // rcx
HMODULE v10; // rax
if ( fdwReason == 1 )
{
OutputDebugStringA("Loaded");
v4 = GetModuleHandleW(L"shell32.dll");
if ( v4 )
{
v5 = GetModuleHandleW(L"gdi32.dll");
ExtTextOutW = (BOOL (__stdcall *)(HDC, int, int, UINT, const RECT *, LPCWSTR, UINT, const INT *))GetProcAddress(v5, "ExtTextOutW");
if ( ExtTextOutW )
sub_180001000("gdi32.dll", (__int64)ExtTextOutW, (__int64)sub_180001120, (__int64)v4);
v7 = GetModuleHandleW(L"api-ms-win-core-libraryloader-l1-2-0.dll");
LoadStringW = (int (__stdcall *)(HINSTANCE, UINT, LPWSTR, int))GetProcAddress(v7, "LoadStringW");
if ( LoadStringW )
{
v9 = "api-ms-win-core-libraryloader-l1-2-0.dll";
LABEL_9:
sub_180001000(v9, (__int64)LoadStringW, (__int64)sub_180001100, (__int64)v4);
goto LABEL_10;
}
v10 = GetModuleHandleW(L"api-ms-win-core-libraryloader-l1-1-1.dll");
LoadStringW = (int (__stdcall *)(HINSTANCE, UINT, LPWSTR, int))GetProcAddress(v10, "LoadStringW");
if ( LoadStringW )
{
v9 = "api-ms-win-core-libraryloader-l1-1-1.dll";
goto LABEL_9;
}
}
LABEL_10:
DisableThreadLibraryCalls(hinstDLL);
}
return 1;
}
2.2.2 sub_180001000功能的代码如下:
__int64 __fastcall sub_180001000(LPCSTR lpString2, __int64 a2, __int64 a3, __int64 a4)
{
_DWORD *v8; // rbx
unsigned int i; // eax
__int64 *v10; // rbx
DWORD flOldProtect; // [rsp+40h] [rbp+8h] BYREF
if ( !lpString2 || !a2 || !a3 )
return 0i64;
v8 = (_DWORD *)(a4 + *(unsigned int *)(*(int *)(a4 + 60) + a4 + 144));
for ( i = v8[3]; i; v8 += 5 )
{
if ( !lstrcmpiA((LPCSTR)(a4 + i), lpString2) )
break;
i = v8[8];
}
if ( !v8[3] )
return 0i64;
v10 = (__int64 *)(a4 + (unsigned int)v8[4]);
if ( !*v10 )
return 0i64;
do
{
if ( *v10 == a2 )
break;
++v10;
}
while ( *v10 );
if ( !*v10 )
return 0i64;
VirtualProtect(v10, 8ui64, 0x40u, &flOldProtect);
*v10 = a3;
VirtualProtect(v10, 8ui64, flOldProtect, &flOldProtect);
return 1i64;
}
2.2.3 sub_180001120代码如下:
BOOL __fastcall sub_180001120(HDC a1, int a2, int a3, UINT a4, const RECT *a5, const WCHAR *a6, UINT a7, const INT *a8)
{
BOOL result; // eax
if ( a4 || !a7 )
result = ExtTextOutW(a1, a2, a3, a4, a5, a6, a7, a8);
else
result = 1;
return result;
}
2.3 结论
通过分析加猜测(后边也证实了)相关逻辑是启动进程时加载此dll,此dll在加载时进程导入表Hook,对shell32.dll中导入的gdi32.dll的ExtTextOutW函数进行挂钩,然后过滤相关字符串。
3、代码实现
完整代码如下:
- 头文件
typedef BOOL(*EXTTEXTOUTW)(HDC hdc, int x, int y, UINT options, RECT* lprect, LPCWSTR lpString, UINT c, INT* lpDx);
typedef int (*GETIAT)(PVOID, BOOLEAN, USHORT, PULONG);
typedef int(__fastcall* LOADSTRINGW)(HINSTANCE, UINT, LPWSTR, int);
extern HMODULE hShell32;
extern HMODULE hGdi32;
extern HMODULE hDbgHelp;
extern EXTTEXTOUTW pExtTextOutW;
extern GETIAT pGetIAT;
void InitializeHook();
BOOL HookFunction(LPCSTR szDllName, PVOID pFuncAddress, PVOID pHookFuncAddess, HMODULE hMod);
BOOL __stdcall MyExtTextOutW(HDC hdc, int x, int y, UINT options, RECT* lprect, LPCWSTR lpString, UINT c, INT* lpDx);
int __fastcall MyLoadStringW(HINSTANCE hInstance, UINT uID, LPWSTR lpBuffer, int cchBufferMax);
- cpp文件
#include "pch.h"
#include "Implement.h"
#include <atlstr.h>
#include <DbgHelp.h>
#pragma comment(lib, "dbghelp.lib")
#pragma comment(linker, "/EXPORT:DllCanUnloadNow=explorerframe.DllCanUnloadNow,@1")
#pragma comment(linker, "/EXPORT:DllGetClassObject=explorerframe.DllGetClassObject,@2")
HMODULE hShell32 = NULL;
HMODULE hGdi32 = NULL;
HMODULE hDbgHelp = NULL;
EXTTEXTOUTW pExtTextOutW = NULL;
GETIAT pGetIAT = NULL;
void InitializeHook()
{
do
{
hShell32 = GetModuleHandleW(L"shell32.dll");
if (hShell32 == NULL)
{
break;
}
hGdi32 = GetModuleHandleW(L"gdi32.dll");
if (hGdi32 == NULL)
{
break;
}
pExtTextOutW = (EXTTEXTOUTW)GetProcAddress(hGdi32, "ExtTextOutW");
if (pExtTextOutW == NULL)
{
break;
}
HMODULE hModuleApiMsWinCoreLibraryloader_l1_2_0 = GetModuleHandleW(L"api-ms-win-core-libraryloader-l1-2-0.dll");
LOADSTRINGW pLoadStringW = (LOADSTRINGW)GetProcAddress(hModuleApiMsWinCoreLibraryloader_l1_2_0, "LoadStringW");
if (pLoadStringW)
{
HookFunction("api-ms-win-core-libraryloader-l1-2-0.dll", pLoadStringW, MyLoadStringW, hShell32);
}
else
{
HMODULE hModuleApiMsWinCoreLibraryloader_l1_1_1 = GetModuleHandleW(L"api-ms-win-core-libraryloader-l1-1-1.dll");
pLoadStringW = (LOADSTRINGW)GetProcAddress(hModuleApiMsWinCoreLibraryloader_l1_1_1, "LoadStringW");
if (pLoadStringW)
{
HookFunction("api-ms-win-core-libraryloader-l1-1-1.dll", pLoadStringW, MyLoadStringW, hShell32);
}
}
BOOL OK = HookFunction("gdi32.dll", pExtTextOutW, MyExtTextOutW, hShell32);
} while (false);
}
int __fastcall MyLoadStringW(HINSTANCE hInstance, UINT uID, LPWSTR lpBuffer, int cchBufferMax)
{
if (uID - 62000 <= 1)
{
return 0;
}
else
{
return LoadStringW(hInstance, uID, lpBuffer, cchBufferMax);
}
}
BOOL HookFunction(LPCSTR szDllName, PVOID pFuncAddress, PVOID pHookFuncAddess, HMODULE hMod)
{
if ((szDllName == NULL) ||
(pFuncAddress == NULL) ||
(pHookFuncAddess == NULL) ||
(hMod == NULL))
{
return FALSE;
}
ULONG ulSize = 0;
PIMAGE_IMPORT_DESCRIPTOR pImportDescriptor =
(PIMAGE_IMPORT_DESCRIPTOR)ImageDirectoryEntryToData(
hMod,
TRUE,
IMAGE_DIRECTORY_ENTRY_IMPORT,
&ulSize);
CStringA strModuleName = "";
while (pImportDescriptor->Name)
{
PSTR pszModuleName = (PSTR)((PBYTE)hMod + pImportDescriptor->Name);
strModuleName = pszModuleName;
if (strModuleName.CompareNoCase(szDllName) == 0)
{
break;
}
pImportDescriptor++;
}
if (strModuleName.GetLength() > 0)
{
PIMAGE_THUNK_DATA pThunk =
(PIMAGE_THUNK_DATA)((PBYTE)hMod + pImportDescriptor->FirstThunk);
while (pThunk->u1.Function)
{
PROC* ppfn = (PROC*)&pThunk->u1.Function;
BOOL bFound = (*ppfn == pFuncAddress);
if (bFound)
{
MEMORY_BASIC_INFORMATION mbi = { 0 };
VirtualQuery(
ppfn,
&mbi,
sizeof(MEMORY_BASIC_INFORMATION)
);
VirtualProtect(
mbi.BaseAddress,
mbi.RegionSize,
PAGE_READWRITE,
&mbi.Protect
);
*ppfn = ((PROC)pHookFuncAddess);
VirtualProtect(
mbi.BaseAddress,
mbi.RegionSize,
mbi.Protect,
&mbi.Protect
);
return TRUE;
}
pThunk++;
}
}
return FALSE;
}
BOOL __stdcall MyExtTextOutW(HDC hdc, int x, int y, UINT options, RECT* lprect, LPCWSTR lpString, UINT c, INT* lpDx)
{
BOOL bResult = FALSE;
//CString str = lpString;
//if (str.Find(L"测试模式") != -1 ||
// str.Find(L"Windows ") != -1 ||
// str.Find(L"Build ") != -1)
//{
// return TRUE;
//}
if ((options != 0) || (c == 0))
{
return ExtTextOutW(hdc, x, y, options, lprect, lpString, c, lpDx);
}
return TRUE;
}
BOOL APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
InitializeHook();
DisableThreadLibraryCalls(hModule);
}
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
4、PS
关于自定义ExtTextOutW,用上边代码可以去除一般水印,也可以使用函数中注释的代码去除指定水印。
BOOL __stdcall MyExtTextOutW(HDC hdc, int x, int y, UINT options, RECT* lprect, LPCWSTR lpString, UINT c, INT* lpDx)
{
BOOL bResult = FALSE;
CString str = lpString;
if (str.Find(L"测试模式") != -1)
{
return TRUE;
}
return ExtTextOutW(hdc, x, y, options, lprect, lpString, c, lpDx);
}
上边代码只把测试模式去掉,效果如下
或者:
BOOL __stdcall MyExtTextOutW(HDC hdc, int x, int y, UINT options, RECT* lprect, LPCWSTR lpString, UINT c, INT* lpDx)
{
BOOL bResult = FALSE;
CString str = lpString;
if (str.Find(L"测试模式") != -1 ||
str.Find(L"Windows ") != -1)
{
return TRUE;
}
return ExtTextOutW(hdc, x, y, options, lprect, lpString, c, lpDx);;
}
上边代码把测试模式、Windows都去掉,效果如下:
或者:
BOOL __stdcall MyExtTextOutW(HDC hdc, int x, int y, UINT options, RECT* lprect, LPCWSTR lpString, UINT c, INT* lpDx)
{
BOOL bResult = FALSE;
CString str = lpString;
if (str.Find(L"测试模式") != -1 ||
str.Find(L"Windows ") != -1||
str.Find(L"Build ") != -1)
{
return TRUE;
}
return ExtTextOutW(hdc, x, y, options, lprect, lpString, c, lpDx);;
}
以是代码把之前的三行水印都去除,效果如下:
5、特别注意
完成相关开发后的dll要修改相应的注册表后重启explorer.exe进程起效,注册表修改的路径为HKEY_CLASSES_ROOT\CLSID\{ab0b37ec-56f6-4a0e-a8fd-7a8bf7c2da96}\InProcServer32 默认值。
在不需要进行去除水印时应将注册表值修改回%SystemRoot%\system32\explorerframe.dll。