Maxim Menshikov

Static analysis reseacher and startup founder


How it works: Full Unlock (WP7) How it works, Retrospects

Have you been wondering how full unlock for Windows Phone 7 works? I’ve been asked multiple times, but it seems like right time to talk about has just came.

Unfortunately, Full Unlock source code isn’t released and never will be. However, here is a little walk around the underlying code.

Full Unlock consists of multiple parts.

  1. Cloaking Filter (clkflt).
  2. Loader Verifier (ulv).
  3. Policy Engine (upl).
  4. Policy Engine Helper (uplhlp).
  5. Account Manager (accman).
  6. Root Manager (accountmanager).

We’ll talk about each of those step-by-step. But first let’s discuss things we should take into consideration.

System file integrity

Yeah, that’s the most important. We have to make unlock that won’t be visible by OS that easily.

Updatability

Second important thing. Windows Phone update process uses update CABs – special archives containing DIFF-files. That DIFF contains information how to patch a binary to make a transition from version %n% to version %n+1%.
Here’s what I mean: system files shouldn’t be altered anyhow!

Performance

Security checks are performed every second or even more often. If unlock becomes a bottleneck, there’s nothing good in such unlock.

Stability

Deriving from previous point… If anything hangs in kernel mode, it is critical. (This behavior can be altered, but let it be)

How the whole chain looks like?

0) System loads Loader Verifier and Policy Engine libraries.
1) System uses ## Loader Verifier to check if selected DLL/EXE has a right to be loaded at all.
2) When DLL/EXE calls certain functions, system calls Policy Engine to check if app has a privilege to use this method.
Simple as that.

Interesting moments in the original chain:

1) Loader Verifier’s integrity isn’t guaranteed. It is in module form (“dissected sections”) and nobody really cares about it. However, system assumes that XIP isn’t writeable (and it isn’t! unless if you’re in System Update mode)
2) One Loader Verifier patch isn’t enough for making everything unlocked
3) Policy Engine is in DLL form and is signed. Therefore, Loader Verifier checks it all for integrity.
4) What if we replaced Loader Verifier? Basically, then we can load “bad” (unsigned) Policy Engine and do all the weird stuff.

So, based on that, how modified chain looks like?

-1) System loads file system drivers. At that moment, Cloaking filter comes to game! It effectively hooks all file system functions and alters them in order to allow secret loading of libraries. i.e. if system asks for \Windows\PolicyEngine.dll, we load \Windows\upl.dll, but saying we loaded \Windows\PolicyEngine.dll.
0) System tries to load original LoaderVerifier and Policy Engine, but gets modified versions loaded
1) All DLLs/EXEs are getting loaded without any problems because of our edited Loader Verifier.
2) Modified Policy Engine checks capabilities not only based on Microsoft rules, but also on rules set by Root Manager application.

Ok, now let’s get back to full unlock components.

Cloaking filter – CLKFLT.dll

This module is still called FsPerf in my code, considering its original idea of improving I/O performance in Windows Mobile 6.x. Then it was simply transformed into something new.
Filter is mostly based on public Windows CE File System Filter examples. The differences are in CreateFile, GetFileAttributes and CloseFile implementations.
Basically, here’s what happens in FILTER_CreateFileW:

HANDLE FILTER_CreateFileW(
    PVOLUME               pvol,
    HANDLE                hProc,
    LPCWSTR               lpFileName,
    DWORD                 dwAccess,
    DWORD                 dwShareMode,
    LPSECURITY_ATTRIBUTES lpSecurityAttributes,
    DWORD                 dwCreate,
    DWORD                 dwFlagsAndAttributes,
    HANDLE                hTemplateFile
    )
{
	lpFileName = Cloaking_GetNewPath((wchar_t*)lpFileName);
	EnsureCacheLoaded();
	...
}

Got the idea? Yes, it is very simple. I just override file name for every single I/O operation. Behind Cloaking_GetNewPath routine there is a simple vector pointing to routing tables. Routing table is in registry. If you cooked a ROM based on Dynamics, you probably had seen a strange package called “Pkg_XipUnlock” in NK (aka XIP – eXecute In Place) partition.
FILTER_Package
It isn’t much interesting, in fact. DSM file is a Device-Side Manifest containing information about package itself. Folder called “CLKFLT.dll” is our filter, “reversmodded” (in other words, that’s a folder with all EXE file sections dissected). RGU is a registry entries set.

REGEDIT4

[HKEY_LOCAL_MACHINE\System\StorageManager\IMGFS\Filters\clkflt]
;[HKEY_LOCAL_MACHINE\System\StorageManager\Filters\clkflt]
"Dll"="clkflt.dll"
"Order"=dword:0

[HKEY_LOCAL_MACHINE\Software\OEM\Cloaking]
"\\windows\\lvmod.dll"="\\windows\\ulv.dll"
"\\windows\\mslvmod.dll"="\\windows\\ulvmod.dll"

Three last lines lead us to explanation about ulv and the rest of stuff.

uLV – ultrashot’s Loader Verifier

Loader Verifier checks every single executable file being loaded by system. I won’t fall into explanation of everything.
It allows every executable to be loaded. Important: LOADED, not allowed to do everything.
Core element is here:

HRESULT LVModProvisionSecurityForApplication(...)
{
	...
	HRESULT res = extLVModProvisionSecurityForApplication(szSID, szAppFriendlyName, pszCaps, dwCaps, szOnePEFilePath, fNativeApp);
	RETAILMSG(DEBUGLOG, (L"[%X][LoaderVerifier] \tresult = %X\r\n", ticks, res));
	
	if (fNativeApp == FALSE)
	{
		if (szSID)
		{
			AddToThirdPartyGroup((LPWSTR)szSID);
		}
	}
	res = S_OK;
	SetLastError(S_OK);
	...
}

If we speak about LVModAuthenticateFile – which is also very important, -its main purpose is to “Allow everyone but fake Root Manager”. Root Manager’s DLLs are thoroughly tested before being loaded, and if their integrity is broken, it won’t load at all.

uPL – ultrashot’s Policy Engine

Here comes the most interesting element. It investigates if certain application has a right to use function it is asking for.
It isn’t much interesting code-wise as it does ## really boring policy checks.
Note: rules system asks Policy Engine for are called “branches” here. They look like this: /LOADERVERIFIER/GLOBAL/AUTHORIZATION/UNSIGNEDNATIVEDLL_AUTHZ, which is pretty much self-explaining.
Pre-check (before calling into original Policy Engine):

if (IsRevokeCertsBranch(hIri) == TRUE)
{
	// REJECTED
	...
}
else if (IsInThirdPartyGroup(accountName))
{
	__try
	{
		if (IsUnsignedNativeDllBranch(hIri))
		{
			BOOL priv = GetPrivileged(accountName);
			if (priv)
			{
				// OK
				...
			}
			else
			{
				FULLUNLOCK_POLICY_MESSAGE msg;
				memset(&msg, 0, sizeof(msg));
				msg.type = ACCESS_DENIED;
				wcscpy_s(msg.userAccount, 200, accountName);
				HRESULT hr = IriGetAsString(hIri, 0, 0xFFFFFFFF, 0,
					msg.requestedAccess, 500, NULL);
				if (hr == S_OK)
					PolicyMsgQueue_Write(msg);
				
				// REJECTED
				...
			}
			*processed = TRUE;
		}
	}
	__except (EXCEPTION_EXECUTE_HANDLER)
	{
		// REJECTED
	}
}
else
{
	// OK
}

As you can see from a source above, this code checks Revoke Certs policy branch. This branch is used to check if certain certificate is blocked or not, and it is the most common reason of access rejections. Second thing code does is notifying our dear Policy Engine Helper (uplhlp) about branch being accessed.

Post Check (after calling into original Policy Engine and getting rejection).

if (hIri)
{
	if (IsWipeBranch(hIri) == TRUE)
	{
		// REJECTED
	}
}
if (IsFullTrustModeEnabled() == TRUE)
{
	// OK
	...
}

if (wcscmp(accountName, ACCID_ACCOUNTMANAGER) == 0)
{
	// OK
	...
}

result = GetPrivileged(accountName);
resultReason = 5;
if (hIri)
{
	if (result == TRUE)
	{
		if (IsAdbFunctionBranch(hIri) == TRUE)
		{
			// REJECTED
		}
	}
}

That’s also something important. Most of homebrew apps cause this code to work. As you may see, if Full Trust mode is enabled, everything is allowed. If app has Root Manager’s Security Identifier (SID), then it is also allowed (remember – we secured Root Manager from being hijacked). Account Database functions are only allowed for Root Manager, and Hard Reset function is disabled for all apps at all.

Ok, let’s head over to …

Policy Engine Helper (UPLHLP)

Is is as simple as possible. Just look at this:
Policy Engine
Its code:

int _tmain(int argc, _TCHAR* argv[])
{
	HANDLE hMsgQueue = GetPolicyMsgQueue(TRUE);
	if (hMsgQueue)
	{
		while (true)
		{
			if (WaitForSingleObject(hMsgQueue, INFINITE) == 0)
			{
				DWORD dwBytesRead = 0;
				DWORD dwMsgFlags = 0;
				FULLUNLOCK_POLICY_MESSAGE msg;
				if (ReadMsgQueue(hMsgQueue, &msg, sizeof(FULLUNLOCK_POLICY_MESSAGE), &dwBytesRead, 5000, &dwMsgFlags) == TRUE)
				{
					wchar_t productID[100];
					GetProductID(msg.userAccount, productID, 100);
					GUID guid;
					CLSIDFromString(productID, &guid);
					CApplicationInfo *info = NULL;
					GetApplicationInfoByProductID(guid, &info);
					if (info)
					{
						if (info->AppInstallType() == 3)
						{
							MESSAGETOASTDATA data;
							data.appId = info->GetAppID();

							wchar_t *newTitle = GetLocalizedString(info->GetTitle());
							if (newTitle)
							{
								data.pszText1 = newTitle;
								data.pszText2 = GetTranslation(RequiresRootAccess);

								wchar_t uri[2000];
								swprintf(uri, L"app://794eb6ae-b2f9-4bfb-9277-4161e4d9e3f5/_default?promote=%ls&promoteaccess=%ls", productID, msg.requestedAccess);
								data.pszTaskUri = uri;
								data.pszSound = NULL;
								HRESULT hr = SHPostMessageToast(&data);
								delete[] newTitle;
							}
						}
						delete info;
					}
				}
			}
		}
	}
	CloseHandle(hMsgQueue);


	return 0;
}

In short: we wait for messages from UPL, get application’s title (if needed), translate it if Title string has a reference to MUI, and then show Toast notification. Can’t be any easier, in my humble opinion.

Account Manager (accman)

Ah, it is too boring. It just adjusts account permissions based on information from registry like that:

[HKEY_LOCAL_MACHINE\Software\OEM\accman]
"{9cefc0bf-7060-45b0-ba66-2d1dcad8dc3c}"="" ;that's unique identifier of my Ringtone Manager.

Root Manager (Account Manager)

This one is a very popular application changing application permissions. Nothing too much interesting: works via COM/Interop, adjusts permissions in the same way ## Accman does it.

FYI

The first version of unlock has been written within two days.
Main problems I had during development:

  1. Make sure you use “GOOD” compiler. ARMv4/ARMv5 compiler from Windows Mobile 6 SDK is good enough, though for latest releases I used one from CE7.
  2. Make sure you enable /LARGEADDRESSAWARE option. XIP modules are located within kernel memory space – and 0x80000000-0xFFFFFFFF address support is really important.
  3. /NXCOMPAT – DEP support. Windows Phone 7 indeed supports NX and it is important to enable this option for kernel modules like those (unless you disabled NX via registry)
  4. Reversmodding. Luckily, those modules aren’t ones with Z-relocation tables, but you still have to dissect .DLL to sections.
  5. Keep K flag in DSM! K flag means “Kernel module”. Otherwise module won’t boot at all.

Conclusion

While overall the whole idea of ## Full Unlock may sound scary, its implementation is very simple. If you don’t understand something from the code snippets posted above, don’t worry: it is alright. Some functions might be unknown for you and I don’t really want to show their implementation, some concepts might sound hard. But they aren’t if you understand their meaning.