I have thought carefully before adding this as "Another Answer" rather than editing my existing answer that I posted above this morning.
I feel that this answer deserves another post of its own as it's not just a simple continuation of the material posted in my answer above.
I have spent over 2 hours on this post to avoid just cutting and pasting content from other sites.
What I have focussed on rather, is on the point that I only include content in this post here, such that this answer would still remain relevant even if the original websites (from where this content is taken from) would ever go down in the future.
I am trying to focus on the essentials that would enable us to hook and modify the code in UWP Apps, as asked by the OP.
Hacking and Modding Windows Universal Apps :
DLL-Injection and Function-Hooking, work perfectly fine in UWP-Apps with most, if not all, injection and hooking techniques.
A couple of main differences regarding Function Hooking and Dll Injection, between the UWP apps and the "standard" Win32 apps :
First:
The Window, in which the UWP app renders its content, is not owned by the Apps
executable.
Instead “ApplicationFrameHost” does,and so you should not target the Window but rather 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.
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 from
StackOverflow (so don’t mind the “goto”s) to set the permissions programmatically.
Code-Snippet
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.
Taking control over the (hidden) “C:\Program Files\WindowsApps\” directory :
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.
Here’s an example that does exactly what just described, using the previously mentioned MinHook library.
Code-Snippet :
#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);
}
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.
If we want to do something to an App before it is launched, we can suspend it before that using the code below.
Code Snippet :
// 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:
Code Snippet:
// 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.
#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();
References:
(1) Basic and Intermediate Techniques of UWP App Modding
(2) Hacking and Modding Windows Universal Apps
I sincerely hope that these two answers of mine would be helpful to anyone searching SE in future on the topics of UWP App hooking/modification.