Introduction
As we know, Windows CE threads can migrate between processes while executing API calls, it borrows the caller's thread and places it in the server process. Such servers are called "Protected Server Libraries" (PSLs) and are processes that export services like DLLs.
Protected Server Libraries (PSL) are used by Windows CE to create API sets that in their own process address space and can server multiple clients concurrently. Windows CE implements all of its system APIs using this technique including all WIN32 APIs, User interface, device driver, and many of its DirectX interfaces. There is a one to one mapping between an API call and its corresponding PSL entry.
Windows CE implements PSL support through a clever technique generating special processor fault conditions that cause the execution of PSL entry points. The system maintains a table of 32 PSL entries. All entries between 0 and 24 are preassigned and may not be used for custom PSLs. PSLs register their APIs, associating their API with one particular entry in the PSL table. Part of the PSL registration process is providing the system with a function table of all the API entry points for a particular PSL. There are also a set of macros defined in psyscall.h which take the PSL id and an index into the registered function table to indicate which particular function for the specified PSL is to be called. This macro generates a 'function pointer' that really is an invalid memory address. When this function pointer gets used, it causes the processor to fault. The fault handling code then inspects the faulty address and sees that it is one of the special PSL addresses and extracts the PSL id and function table index. It then turns around and calls the correct entry point in the PSL, just as if the caller had directly jumped to it – with one significant difference in that process address spaces have been jumped across as well. When the PSL function finishes execution and returns, the OS resumes the application that called the PSL to begin with by returning from the fault condition with the return value from the PSL function. The application resumes execution and it looks just as if a function call had occurred. Because PSLs are the core way APIs are implemented in the system, the code path for executing this process is very streamlined.
PSLs have four main components:
- Functions implementing the various entry points of the PSL.
- Function table (a
Vtable
) that is an array of function pointers containing all the entry points of the PSL. This is an array of pointers to the functions making up the API set (PSL entries). The first two entries are reserved for the kernel. The first entry is the notification procedure that the kernel will call any time a thread or a process is stopped or started, low memory occurs, or the system is booted. The second entry is reserved by the kernel and should be zero. All remaining entries are available to the PSL implementer. - Signature table describing all the argument types for each function in the function table. Made up of an array of 2 or more entry points with a one to one correspondence to the API function table. You can use these macros: FNSIG0-12, DW - DWORD, PTR - Pointer, I64 - 64 bit integer. PSL functions can have one of three argument types (DW, PTR, I64) or no arguments at. Those types are a
DWORD
(unsigned long), a pointer (void *
), or a 64 bit integer (long long
). There return values are the same. They can be used in any combination within a function or across function. There can be no other parameter types but the current parameter types can be used to contain other types (like a WORD
or WCHAR
in a DWORD
, a BSTR
in a void *
). PSL functions can have from zero to 12 arguments. As can be seen above, each PSL function signature is defined by using an FNSIG
macro that is followed by the number of arguments the function has. The FNSIG
macro is then filled in with an argument type specification for each argument it has.
The data in the signature table is used by the kernel to marshal arguments for the PSL function calls across process address spaces so it is imperative that these be correct or arguments will not get passed correctly from a client calling your PSL into the actual function in your PSL that implements the API call.
- A .lib that clients of the PSL link to which contains the code that generates the special invalid memory address that causes the processor to fault and execute the PSL. The code also can be inline functions defined in the header file.
Background
As I know, there are two ways to hook for primitive and one for Expert:
- Basic Hooking
- Advanced Hooking
BASIC HOOKING
One may suggest that in order to hook a system function (from application located in the ROM of the device), it is enough to replace the APISET
functions pointers. This will work partially. This technique will intercept only the calls from part of the programs on the device: all third party applications and some of the ones in the ROM (depending which team in MS has built them probably). It will not intercept calls from the kernel itself.
The reason for this is that when the whole OS image is built with Platform Builder, the addresses of all system functions are known. So in order to gain efficiency, all system calls are replaced with the appropriate address. They do not go through the whole APISET
mechanism (which is fast but not as fast as the direct call).
ADVANCED HOOKING
If we want to intercept all possible calls - other technique is required. We should place our hook at the address that kernel calls for particular function. We cannot replace the existing code there - since it is located in ROM. So the only choice left is to tweak the virtual memory (VM) mappings and fool the system.
static void InitializePageSize()
{
PAGE_SIZE = UserKInfo[KINX_PAGESIZE];
if (PAGE_SIZE == 4096) {
VA_PAGE = 12;
PAGE_MASK = 0x00F;
} else {
VA_PAGE = 10;
PAGE_MASK = 0x03F;
}
}
VM is an old concept. Basically, the OS keeps table(s) describing mappings between the physical memory pages and virtual memory addresses (all applications work with virtual addresses and the pointer in the APISET tables are virtual).
We do the following steps:
- Find the virtual address of the function we want to hook
- Find the physical page mapping (in VM tables) for this address
- Allocate new physical pages and copy our code in it
- Switch the mapping found in step (2) to point to the page allocated in (3)
By carefully designing the code in step (3), we are able to intercept the calls without losing the capability to call the original function.
We got this Value form Platform Builder:
#define W32_SetKMode 108
#define W32_CreateAPISet 2
#define W32_CacheSync 34
#define W32_SetProcPermissions 72
#define W32_QueryAPISetID 112
#define W32_LockPages 138
#define W32_GetRomFileInfo 32
#define W32_GetCallerProcess 68
#define W32_GetProcAddrBits 63
#define W32_GetProcFromPtr 61
#define W32_GetOwnerProcess 67
#define GetProcAddrEx WIN32_CALL(FARPROC,GetProcAddressW,(HMODULE,LPCWSTR))
#define QueryAPISetID WIN32_CALL(int, QueryAPISetID, (char *))
#define GetProcAddrBits WIN32_CALL(DWORD, GetProcAddrBits, (HANDLE))
#define GetCallerProcess WIN32_CALL(HANDLE, GetCallerProcess, (void))
#define GetOwnerProcess WIN32_CALL(HANDLE, GetOwnerProcess, (void))
#define GetProcFromPtr WIN32_CALL(HANDLE, GetProcFromPtr, (LPVOID))
#define GetKPhys WIN32_CALL(BOOL, GetKPhys, (void *, DWORD))
#define ProcGetIndex(h) ((DWORD (*)(HANDLE))IMPLICIT_CALL(SH_WIN32,W32_ProcGetIndex))(h)
#define LockPages WIN32_CALL(BOOL, LockPages,
(LPVOID lpvAddress, DWORD cbSize, PDWORD pPFNs, int fOptions))
#define SetProcPermissions WIN32_CALL(DWORD, SetProcPermissions, (DWORD))
#define SetKMode WIN32_CALL(BOOL, SetKMode, (BOOL))
#define CreateAPISet WIN32_CALL(HANDLE, CreateAPISet,
(char acName[4], USHORT cFunctions, const PFNVOID *ppfnMethods, const DWORD *pdwSig))
#define RegisterAPISet _APISET_CALL(BOOL, REGISTER, (HANDLE hASet, DWORD dwSetID))
#define CacheSync WIN32_CALL(void, CacheSync, (int flags))
#define GetRomFileInfo WIN32_CALL(void, GetRomFileInfo,
(DWORD type, LPWIN32_FIND_DATA lpfd, DWORD count))
MEMBLOCK*
GetMemBlock(void *ptr) {
PSECTION pscn =
SectionTable[ ((ulong)ptr >> VA_SECTION) & SECTION_MASK];
return (*pscn)[((ulong)ptr >> VA_BLOCK) & BLOCK_MASK];
}
DWORD GetGwesBaseAddress(HANDLE *pHandle) {
DWORD *po = (DWORD *)UserKInfo[KINX_APISETS];
po += SH_GDI;
CINFO *pogdi = (CINFO *)(*po);
PPROCESS p = (PPROCESS)pogdi->pServer;
if (pHandle)
*pHandle = p->hProc;
return (p->dwVMBase);
}
To get Address of Gwes inside memory:
InitializePageSize();
SetKMode(1);
SetProcPermissions(0xffffffff);
This very important code is to set Access to patch your low level code.