如何对 Windows 10 UWP 应用进行逆向工程?

逆向工程 艾达 调试 视窗 10
2021-07-06 01:16:49

我想看看当您在 Windows 10 的“设置”->“更新”窗口中单击“立即重新启动”按钮时,Microsoft 正在做什么:

在此处输入图片说明

不知何故,结果与通过InitiateSystemShutdownExInitiateShutdownWinAPI可用的结果不同,尤其是在安装更新方面。

所以我想查看他们用于该Restart按钮的代码通常我会使用 Spy++ 来查找父窗口WndProc,然后在 IDA Pro 中加载进程并在其上放置一个断点。然后捕获BM_CLICK消息的条件

但在这种情况下,整个设置是一个自己的窗口。该按钮不会出现在 Spy++ 中:

在此处输入图片说明

任何想法如何从那里开始?

4个回答

在将其添加为“另一个答案之前,我仔细考虑过,而不是编辑我今天早上在上面发布的现有答案。

我觉得这个答案值得再写一篇文章,因为它不仅仅是我上面的答案中发布的材料的简单延续。

我在这篇文章上花了 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 间谍工具(请注意,也可以使用许多其他工具)......

使用 UI Spy 工具的 RESTART 按钮的属性

现在我们将看到当我们点击 RESTART 按钮时我们 BREAK ON 的代码:

首先,运行系统设置管理器SystemSettings.exe将在任务管理器中看到)。使用调试器
附加”到SystemSettings.exe进程(为此我使用了 [excellent] x64dbg Debugger)。
确保在调试器设置中勾选中断 DLL 加载,如下所示:

DLL 加载

现在我们*单击“立即重启”按钮*(Windows 设置窗口的)。当“ SettingsHandlers_nt.dll ”加载到进程中时,我们将中断调试器

这是处理设置窗口事件(点击等)的主要 dll。

已加载 SettingsHandlers_nt.dll

我们在 SettingsHandlers_nt.dll 模块中登陆

这是控制流图的一部分,它实际上决定重新启动:

以单用户模式重新启动

不知何故,结果与通过 InitiateSystemShutdownEx 或 InitiateShutdown WinAPI 获得的结果不同,尤其是关于安装更新。

OP 是对的。微软更新后[自动]重启
实际决策部分MusUpdateHandlers.dll处理,如下所示:

更新后强制重启的 CFG

还有这里:

更新后重新启动的代码视图

我添加了最后 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();  

示例输出(截图):

https://raw.githubusercontent.com/justcoding121/Windows-User-Action-Hook/stable/EventHook.Examples/EventHook.ConsoleApp.Example/Capture.PNG

我敢肯定,一旦您看到我们如何能够全局挂钩各种 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 上设置断点。