Hacking and Modding Windows Universal Apps and Games (UWP)


A picture of Peter Repukat, the author of this post
Peter Repukat September 19th, 2016
Post Header Image

Note:
This article was migrated and used some custom plugins on the old Blog. Some things might look a bit broken, but should be perfectly readable.


There are a lot of misconceptions about Windows 10 UWP Apps.
Most people think that you can’t do anything to them, in terms of modding or hacking.

Well, this is not true at all.

In fact, you can do a whole heck of a lot with them and have fun in all sorts of ways. Including to mod the shit out of them.

Windows 10s UWP Apps are built upon Win32, which we all know and love (and/or hate to the core…)
Windows 8s UWP Apps are a slightly different story but who ever used that shit, right?

Here I’ll give you a quick rundown on how you can hack and mod the shit out of them.
We first begin with just reading and modifying things in memory, go over DLL-Injections and

Misconception #1: Cheating

To kick things off, let’s begin with something real easy… Cheatengine, which can also be used for way more than what its name implies.

Note: I don’t support cheating in (multiplayer) Games, but it’s here to prove a point.

A lot of people seem to think that there at least won’t be much cheating in Games when they are UWP exclusive, at least a single strong point for them, you might think.

But nope, Cheatengine just works perfectly fine. The inbuilt debugger from Cheatengine just plain works, too!
Here is a screenshot of Cheatengine modifying a text string in Forza Motorsport 6: Apex

Cheatengine modifying Forza Motorsport 6: Apex

I’ve also tried and casually played with x64dbg, but didn’t play around all too seriously, but I also expect it to work just fine for more serious usage (outside of cheating).

Misconception #2: Programs like FRAPS cannot and will never Work

This is also everything else but true.
It is correct that FRAPS itself does not work, however, the latest FRAPS release was from February 2013.
Let that sink in for a minute.

But now, let us first look at how Programs like FRAPS, other in-game overlays, recording or benchmarking software even work.

Those programs, basically, work by hooking DirectX’s “End-Scene” call, which, as you might guess, is called at the end of every frame rendering.
Of course, this is slightly different when recording OpenGL or Vulkan or whatever but the general idea is the same.

How do they hook this function? They basically just inject a DLL and then hook the specific method.

So we’re talking about DLL-Injection and Function-Hooking, which also just works perfectly fine in UWP-Apps.  with most, if not all, injection and hooking techniques.

But, and there is always a but, you have to look out for two things.

First:
The Window, in which the UWP app renders its content, is not owned by the Apps executable.
Instead “ApplicationFrameHost” does, and this is where FRAPS falls short since FRAPS directly targets the window, rather than the process itself.
Note: Because of this, you cannot create new windows, like message boxes for example, when injected in a UWP-App

Second:
The DLL you want to inject has to have “Read, Execute” as well as the “Read” permissions set for the “ALL APPLICATION PACKAGES”-Group

Properties

You can set this via the properties tab of the DLL-file but the name may differ depending on your system language.
You could also just use the following little code snippet which I’ve taken from StackOverflow (so don’t mind the “g_oto”_s) to set the permissions programmatically.

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;
}

Afterward, inject your DLL with your preferred injector/method, and your DLLs code will magically function.

Since UWP-Apps use the Win32 API under the hood, you can expect KernelBase.dll, Kernel32.dll, ntdll.dll, and user32.dll to be loaded in them. You will also find d2d1.dll and either d3d11.dll or d3d12.dll (used in a handful of apps) loaded in all UWP apps, including the new UWP calculator app.
For function hooking, as you might now expect, it works the same way it does for Win32 Programs.
A Handy little library which I’ve used for this is MinHook

So recording and benchmarking software and in-game overlays could work just fine.
An example of a perfectly fine working recording software would be Dxtorywhich was updated back in September 2015 to support UWP-Apps!

Misconception #3: You cannot create Mods

Well… Again you very well can create mods!
With Cheatengine, debuggers like x64dbg, and DLL-injection and function hooking working, there is nothing to stop anyone from modding the shit out of any UWP-App.

But let us begin with why this misconception exists in the first place.

Without taking control over the (hidden) “C:\Program Files\WindowsApps\” directory, or wherever you might have it, you cannot access the files of UWP-Apps. But you can just take control of this, and any subdirectories and its files without any problems.

You could also always just open up a shell as NT-Authority and access them that way.

If you just wanted to mod a simple config file or something you should be fine.
However, some Apps, not all of them, check if their files were tampered with. But that’s easily circumvented.

All you have to do is Hook the “CreateFileW“-Method in “KernelBase.dll“, monitor the file access and then reroute those access requests to load your modified version from some directory you can access just fine.

Unfortunately though, this method doesn’t appear to work for sound files or files that are streamed. If anyone has a fix for this, I’d love to know…

Here’s an example that does exactly what just described, using the previously mentioned MinHook library

    #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:\Program Files\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);
        }

A few more things

You can’t just launch a UWP-App like a regular Win32 Program using _CreateProcess.
_Luckily for us, M$ has provided us with  the IApplicationActivationManager interface which lets developers launch UWP apps from regular Win32 programs.

    HRESULT LaunchApplication(LPCWSTR userModelId, PDWORD pdwProcessId)
        {
        
         CComPtr<IApplicationActivationManager> spAppActivationManager;
         HRESULT result = E_INVALIDARG;
         // Initialize IApplicationActivationManager
         result = CoCreateInstance(CLSID_ApplicationActivationManager, NULL, CLSCTX_LOCAL_SERVER, IID_IApplicationActivationManager, (LPVOID*)&spAppActivationManager);
        
         if (!SUCCEEDED(result))
         return result;
        
         // This call ensures that the app is launched as the foreground window
         result = CoAllowSetForegroundWindow(spAppActivationManager, NULL);
        
        
         if (!SUCCEEDED(result))
         return result;
        
         // Launch the app
         result = spAppActivationManager->ActivateApplication(userModelId, NULL, AO_NONE, pdwProcessId);
        
         return result;
        }

You can get the AppUserModelID or “userModelId” called in code from the registry.
It’ is in an AppX_SOMESTRING_ container in HKEY_CLASSES_ROOT
Getting AppUserModelID

If we want to do something to an App before it is launched, we can suspend it before that

    // 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;

Using the code above, your program will hang until the app is resumed as it is waiting on the app to reply back to the IApplicationActivationManager on its launch status. To resume the app, you can simply specify the path to your executable file when enabling debugging:

    // Enable Debugging with a custom debugger executable
        hResult = debugSettings->EnableDebugging(appFullName.c_str(), pathToExecutable.c_str(), NULL);
        if(hResult != S_OK) return hResult;

Windows will pass the process ID for the app process to the executable acting as the debugger using the command line argument -p followed by the process ID. From the debugger executable, you can do whatever you want to while the app is suspended such as injecting mods, and finally resume the app using NtResumeProcess.

Here’s an example of the main() function from such “debugger executable”

    #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;
        }

    // 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;

Using the code above, your program will hang until the app is resumed as it is waiting on the app to reply back to the IApplicationActivationManager on its launch status. To resume the app, you can simply specify the path to your executable file when enabling debugging:

    // Enable Debugging with a custom debugger executable
        hResult = debugSettings->EnableDebugging(appFullName.c_str(), pathToExecutable.c_str(), NULL);
        if(hResult != S_OK) return hResult;

Windows will pass the process ID for the app process to the executable acting as the debugger using the command line argument -p followed by the process ID. From the debugger executable, you can do whatever you want to while the app is suspended such as injecting mods, and finally resume the app using NtResumeProcess.

Here’s an example of the main() function from such “debugger executable”

    #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;
        }

Important note:Call

    // Initialize COM objects, only need to do this once per thread
        DWORD hresult = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
        if (!SUCCEEDED(hresult)) return hresult;

Before you launch an App or do anything call this afterward:

    CoUninitialize();