如何对 Windows 10 UWP 应用进行逆向工程?
在将其添加为“另一个答案”之前,我仔细考虑过,而不是编辑我今天早上在上面发布的现有答案。
我觉得这个答案值得再写一篇文章,因为它不仅仅是我上面的答案中发布的材料的简单延续。
我在这篇文章上花了 2 个多小时,以避免只是剪切和粘贴其他网站的内容。我更关注的是,我只在这篇文章中包含内容,这样即使原始网站(从该内容获取的地方)将来会出现故障,该答案仍将保持相关性.
我正努力按照 OP 的要求,专注于使我们能够挂钩和修改 UWP 应用程序中的代码的基本要素。
黑客和修改 Windows 通用应用程序:
DLL-Injection 和 Function-Hooking,在 UWP-Apps 中使用大多数(如果不是全部)注入和挂钩技术都可以完美地工作。
UWP 应用程序和“标准”Win32 应用程序之间关于函数挂钩和 Dll 注入的几个主要区别:
第一:
UWP 应用程序在其中呈现其内容的窗口不属于应用程序可执行文件。取而代之的是“ ApplicationFrameHost ”,因此您不应以窗口为目标,而应以进程本身为目标。
注意:因此,当注入 UWP 应用程序时,您无法创建新窗口,例如消息框。
第二:
您要注入的 DLL 必须具有“读取、执行”以及为“所有应用程序包”组设置的“读取”权限。您可以通过 DLL 文件的属性选项卡进行设置,但名称可能因您的系统语言而异。
您也可以使用StackOverflow 中的以下小代码片段 (所以不要介意“goto”)以编程方式设置权限。
代码片段
DWORD SetPermissions(std::wstring wstrFilePath) {
PACL pOldDACL = NULL, pNewDACL = NULL;
PSECURITY_DESCRIPTOR pSD = NULL;
EXPLICIT_ACCESS eaAccess;
SECURITY_INFORMATION siInfo = DACL_SECURITY_INFORMATION;
DWORD dwResult = ERROR_SUCCESS;
PSID pSID;
// Get a pointer to the existing DACL
dwResult = GetNamedSecurityInfo(wstrFilePath.c_str(), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, & pOldDACL, NULL, & pSD);
if (dwResult != ERROR_SUCCESS)
goto Cleanup;
// Get the SID for ALL APPLICATION PACKAGES using its SID string
ConvertStringSidToSid(L"S-1-15-2-1", & pSID);
if (pSID == NULL)
goto Cleanup;
ZeroMemory( & eaAccess, sizeof(EXPLICIT_ACCESS));
eaAccess.grfAccessPermissions = GENERIC_READ | GENERIC_EXECUTE;
eaAccess.grfAccessMode = SET_ACCESS;
eaAccess.grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT;
eaAccess.Trustee.TrusteeForm = TRUSTEE_IS_SID;
eaAccess.Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
eaAccess.Trustee.ptstrName = (LPWSTR) pSID;
// Create a new ACL that merges the new ACE into the existing DACL
dwResult = SetEntriesInAcl(1, & eaAccess, pOldDACL, & pNewDACL);
if (ERROR_SUCCESS != dwResult)
goto Cleanup;
// Attach the new ACL as the object's DACL
dwResult = SetNamedSecurityInfo((LPWSTR) wstrFilePath.c_str(), SE_FILE_OBJECT, siInfo, NULL, NULL, pNewDACL, NULL);
if (ERROR_SUCCESS != dwResult)
goto Cleanup;
Cleanup:
if (pSD != NULL)
LocalFree((HLOCAL) pSD);
if (pNewDACL != NULL)
LocalFree((HLOCAL) pNewDACL);
return dwResult;
}
之后,使用您喜欢的注入器/方法注入您的 DLL,您的 DLL 代码将神奇地运行。
由于 UWP 应用程序在底层使用 Win32 API,因此您可以期望在其中加载 KernelBase.dll、Kernel32.dll、ntdll.dll 和 user32.dll。您还会发现 d2d1.dll 和 d3d11.dll 或 d3d12.dll(在少数应用中使用)加载在所有 UWP 应用中,包括新的 UWP 计算器应用。
对于函数挂钩,正如您现在所期望的,它的工作方式与 Win32 程序的工作方式相同。
以控制(隐藏)“C:\ Program Files文件\ WindowsApps \”目录下:
如果不采取控制(隐藏)“C:\ Program Files文件\ WindowsApps \”目录下,或任何你可能拥有它,你不能访问UWP-Apps 文件。
但是您可以控制它,以及任何子目录及其文件,而不会出现任何问题。
您也可以始终以 NT-Authority 身份打开一个 shell并以这种方式访问它们。
如果你只是想修改一个简单的配置文件或其他东西,你应该没问题。但是,某些应用程序(并非所有应用程序)会检查其文件是否被篡改。但这很容易规避。
你所要做的就是钩住“KernelBase.dll”中的“CreateFileW”-Method,监控文件访问,然后重新路由这些访问请求,从某个你可以访问的目录加载你的修改版本。
这是一个使用前面提到的 MinHook 库完成刚才描述的示例。
代码片段:
#include <Windows.h>
#include <atlbase.h>
#include <Shlobj.h>
#include <string>
#include "MinHook.h"
// Path to modified game files store in AppData
std::wstring MOD_FILES_PATH;
// Path to the apps protected resources in WindowsApps
// Don't use the full path name, just keep the Publisher.AppName part
std::wstring APP_LOCATION(L"C:\\ProgramFiles\\WindowsApps\\Publisher.AppName");
// Sets a hook on the function at origAddress function and provides a trampoline to the original function
BOOL setHook(LPVOID * origAddress, LPVOID * hookFunction, LPVOID * trampFunction);
// Attaches a hook on a function given the name of the owning module and the name of the function
BOOL attach(LPWSTR wstrModule, LPCSTR strFunction, LPVOID * hook, LPVOID * original);
// Basic hook setup for CreateFileW
typedef HANDLE(WINAPI * PfnCreateFileW)(LPCWSTR lpFilename, DWORD dwAccess,
DWORD dwSharing, LPSECURITY_ATTRIBUTES saAttributes, DWORD dwCreation,
DWORD dwAttributes, HANDLE hTemplate);
PfnCreateFileW pfnCreateFileW = NULL; // Will hold the trampoline to the original CreateFileW function
// CreateFileW hook function
HANDLE WINAPI HfnCreateFileW(LPCWSTR lpFilename, DWORD dwAccess, DWORD dwSharing, LPSECURITY_ATTRIBUTES saAttributes, DWORD dwCreation, DWORD dwAttributes, HANDLE hTemplate) {
std::wstring filePath(lpFilename);
// Check if the app is accessing protected resources
if (filePath.find(APP_LOCATION) != filePath.npos) {
std::wstring newPath(MOD_FILES_PATH);
// Windows provides the app the location of the WindowsApps directory, so the first half the file path will use back slashes
// After that, some apps will use back slashes while others use forward slashes so be aware of what the app uses
newPath += filePath.substr(filePath.find(L"\\", APP_LOCATION.size()) + 1,
filePath.size());
// Check if the file being accessed exists at the new path and reroute access to that file
// Don't reroute directories as bad things can happen such as directories being ghost locked
if (PathFileExists(newPath.c_str()) && !PathIsDirectory(newPath.c_str()))
return pfnCreateFileW(newPath.c_str(), dwAccess, dwSharing, saAttributes,
dwCreation, dwAttributes, hTemplate);
}
// Let the app load other files normally
return pfnCreateFileW(lpFilename, dwAccess, dwSharing, saAttributes,
dwCreation, dwAttributes, hTemplate);
}
BOOL Initialize() {
// Initialize MinHook
if (MH_Initialize() != MH_OK)
return FALSE;
// Get the path to the apps AppData folder
// When inside a UWP app, CSIDL_LOCAL_APPDATA returns the location of the apps AC folder in AppData
TCHAR szPath[MAX_PATH];
if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, szPath))) {
// Get the path to the mod files folder
std::wstring appData(szPath);
appData = appData.substr(0, appData.rfind(L"AC")); // Get the base directory
appData += L"LocalState\\ModFiles\\"; // Get the location of any new files you want the app to use
MOD_FILES_PATH = appData;
} else
return FALSE;
// Attach a hook on CreateProcessW and return the status of the hook
BOOL hook = TRUE;
hook &= attach(L"KernelBase.dll", "CreateFileW", (LPVOID * ) & HfnCreateFileW,
(LPVOID * ) & pfnCreateFileW);
return hook;
}
BOOL Uninitialize() {
// Uninitialize MinHook
if (MH_Uninitialize() != MH_OK)
return FALSE; // This status will end up being ignored
return TRUE;
}
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH:
return Initialize(); // If initialization failed, the DLL will detach
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
Uninitialize(); // Return value doesn't matter when detaching
break;
}
return TRUE;
}
BOOL setHook(LPVOID * origAddress, LPVOID * hookFunction, LPVOID * trampFunction) {
if (MH_CreateHook(origAddress, hookFunction,
reinterpret_cast < LPVOID * > (trampFunction)) != MH_OK)
return FALSE;
if (MH_EnableHook(origAddress) != MH_OK)
return FALSE;
return TRUE;
}
BOOL attach(LPWSTR wstrModule, LPCSTR strFunction, LPVOID * hook, LPVOID * original) {
HMODULE hModule = GetModuleHandle(wstrModule);
if (hModule == NULL)
return FALSE;
FARPROC hFunction = GetProcAddress(hModule, strFunction);
if (hFunction == NULL)
return FALSE;
return setHook((LPVOID * ) hFunction, hook, original);
}
还有一些事情:
您不能像使用 CreateProcess 的常规 Win32 程序一样启动 UWP 应用程序。幸运的是,M$ 为我们提供了IApplicationActivationManager接口,允许开发人员从常规 Win32 程序启动 UWP 应用程序。
如果我们想在应用程序启动之前对其执行某些操作,我们可以使用以下代码在此之前暂停它。
代码片段:
// Gets the current application's UserModelId and PackageId from the registry
// Substitute your own methods in place of these
std::wstring appName = GetApplicationUserModelId();
std::wstring appFullName = GetApplicationPackageId();
HRESULT hResult = S_OK;
// Create a new instance of IPackageDebugSettings
ATL::CComQIPtr debugSettings;
hResult = debugSettings.CoCreateInstance(CLSID_PackageDebugSettings, NULL, CLSCTX_ALL);
if(hResult != S_OK) return hResult;
// Enable debugging
hResult = debugSettings->EnableDebugging(appFullName.c_str(), NULL, NULL);
if(hResult != S_OK) return hResult;
// Launch the application using the function discussed above
DWORD dwProcessId = 0;
hResult = LaunchApplication(appName, &dwProcessId);
if(hResult != S_OK) return hResult;
/* Do more stuff after the app has been resumed */
// Stop debugging the application so it can run as normal
hResult = debugSettings->DisableDebugging(appFullName.c_str());
if(hResult != S_OK) return hResult;
使用上面的代码,您的程序将挂起,直到应用程序恢复,因为它正在等待应用程序在其启动状态回复 IApplicationActivationManager。要恢复应用程序,您只需在启用调试时指定可执行文件的路径:
代码片段:
// Enable Debugging with a custom debugger executable
hResult = debugSettings->EnableDebugging(appFullName.c_str(), pathToExecutable.c_str(), NULL);
if(hResult != S_OK) return hResult;
Windows 将使用命令行参数 -p 后跟进程 ID 将应用程序进程的进程 ID 传递给充当调试器的可执行文件。从调试器可执行文件中,您可以在应用程序暂停时做任何您想做的事情,例如注入 mod,最后使用 NtResumeProcess 恢复应用程序。
#define IMPORT extern __declspec(dllimport)
IMPORT int __argc;
IMPORT char** __argv;
//IMPORT wchar_t** __wargv;
// Turning this into a normal Windows program so it's invisible when run
int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
DWORD dwProcessId = 0;
// Process the arguments passed to the debugger
for (int i = 1; i < __argc; i += 2)
{
std::string arg(__argv[i]);
if (arg == "-p")
dwProcessId = atoi(__argv[i + 1]);
}
if(dwProcessId == 0)
return E_FAIL;
// Can do additional error checking to make sure the app is active and not tombstoned
ModLoader::InjectMods(dwProcessId);
ProcessUtils::ResumeProcess(dwProcessId); // Uses NtResumeProcess
return S_OK;
}
重要提示:致电
// Initialize COM objects, only need to do this once per thread
DWORD hresult = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
if (!SUCCEEDED(hresult)) return hresult;
在您启动应用程序或执行任何操作之前,请先调用它:
CoUninitialize();
参考资料:
(1)UWP App Modding的基础和中级技术
(2)Hacking和Modding Windows Universal Apps
我真诚地希望我的这两个答案对将来在 UWP App 挂钩/修改主题上搜索 SE 的人有所帮助。
正如所承诺的那样,我回来对OP 提出的问题发表更具体的答案。
决定将其作为一个单独的答案来写,因为我相信这个内容本身就很突出,并且不适合我上面发布的两个答案中的任何一个。
解决他的第一个问题:
但在这种情况下,整个设置是一个自己的窗口。该按钮不会出现在 Spy++ 中
好吧,使用 Spy++ 以外的工具时确实会出现该按钮。
我正在粘贴 2 个工具的屏幕截图,借助这些工具可以发现 RESTART 按钮的属性。
使用作为Microsoft SDK 一部分的检查工具:
并为此使用UI 间谍工具(请注意,也可以使用许多其他工具)......
现在我们将看到当我们点击 RESTART 按钮时我们 BREAK ON 的代码:
首先,运行系统设置管理器(SystemSettings.exe将在任务管理器中看到)。使用调试器
“附加”到SystemSettings.exe进程(为此我使用了 [excellent] x64dbg Debugger)。
确保在调试器设置中勾选中断 DLL 加载,如下所示:
现在我们*单击“立即重启”按钮*(Windows 设置窗口的)。当“ SettingsHandlers_nt.dll ”加载到进程中时,我们将中断调试器。
这是处理设置窗口事件(点击等)的主要 dll。
这是控制流图的一部分,它实际上决定重新启动:
不知何故,结果与通过 InitiateSystemShutdownEx 或 InitiateShutdown WinAPI 获得的结果不同,尤其是关于安装更新。
OP 是对的。微软更新后[自动]重启
的实际决策部分由MusUpdateHandlers.dll处理,如下所示:
还有这里:
我添加了最后 2 个屏幕截图,因为 OP 想知道更新后系统重新启动时调用了哪些 API...
我希望这现在结束了@c00000fd 想知道的答案......
“我想查看有关如何在 UWP 应用程序中挂钩和步入处理按钮单击事件的函数的详细信息。”
我建议使用EventHook 库,它旨在挂钩全局 Windows 用户事件。这可作为Nuget 包使用。
Project Repo(命名为Windows User Action Hook)在 Github 上可用,我的 fork 可以在这里访问。这是针对我上面提到的同一个库(EventHook 库)。如果您想获取源代码,可以访问 Github 存储库。
从Github 项目的 README 页面,让我摘录一些细节:
支持的事件:(这些是您可以全局挂钩的事件)
- 键盘事件
- 鼠标事件
- 剪贴板事件
- 应用事件
- 打印事件
用法 - 通过 nuget 安装
安装包事件钩子
示例代码:
KeyboardWatcher.Start();
KeyboardWatcher.OnKeyInput += (s, e) =>
{
Console.WriteLine(string.Format("Key {0} event of key {1}", e.KeyData.EventType, e.KeyData.Keyname));
};
MouseWatcher.Start();
MouseWatcher.OnMouseInput += (s, e) =>
{
Console.WriteLine(string.Format("Mouse event {0} at point {1},{2}", e.Message.ToString(), e.Point.x, e.Point.y));
};
ClipboardWatcher.Start();
ClipboardWatcher.OnClipboardModified += (s, e) =>
{
Console.WriteLine(string.Format("Clipboard updated with data '{0}' of format {1}", e.Data, e.DataFormat.ToString()));
};
ApplicationWatcher.Start();
ApplicationWatcher.OnApplicationWindowChange += (s, e) =>
{
Console.WriteLine(string.Format("Application window of '{0}' with the title '{1}' was {2}", e.ApplicationData.AppName, e.ApplicationData.AppTitle, e.Event));
};
PrintWatcher.Start();
PrintWatcher.OnPrintEvent += (s, e) =>
{
Console.WriteLine(string.Format("Printer '{0}' currently printing {1} pages.", e.EventData.PrinterName, e.EventData.Pages));
};
Console.Read();
KeyboardWatcher.Stop();
MouseWatcher.Stop();
ClipboardWatcher.Stop();
ApplicationWatcher.Stop();
PrintWatcher.Stop();
示例输出(截图):
我敢肯定,一旦您看到我们如何能够全局挂钩各种 Windows 用户事件。在你的情况下,使用这个库,我们可以编写一个小 C# 程序,例如,它会为“立即重启”按钮挂钩鼠标按下事件,从而允许我们控制后续事件。
由于这个问题是关于挂钩UWP 应用程序,我想举一个例子,其中这个库用于专门挂钩 UWP 应用程序,以便在需要时可以使用该存储库中的代码作为示例。请注意,我并没有直接从该存储库中摘录任何内容,因为我只是想表明该库也可以与 UWP 应用程序很好地配合使用。你可以在这里访问我的Github Repo UWPHook 分支。
我在每个名为 SystemSettting*.* 的文件中的 System32 文件夹中搜索了 Shutdown 一词。我使用了 Total Commander,因为它可以搜索多种编码,例如 ASCII 和 Unicode:
然后内置查看器显示了对InitiateSystemShutdownExW
:
的引用。
来自 MSDN:
InitiateSystemShutdownEx 函数启动指定计算机的关机和可选重启,并可选记录关机原因。
BOOL WINAPI InitiateSystemShutdownEx(
_In_opt_ LPTSTR lpMachineName,
_In_opt_ LPTSTR lpMessage,
_In_ DWORD dwTimeout,
_In_ BOOL bForceAppsClosed,
_In_ BOOL bRebootAfterShutdown,
_In_ DWORD dwReason
);
为了验证,下一步是使用 Ida Pro 或 API Monitor 等工具在此 API 上设置断点。