一种User Mode下访问物理内存及Kernel Space的实现
一.背景
WinCE发展到6.0之后,内存结构和管理方法进行了完善。对应用程序影响比较大的有Virtual Memory Layout的变化,如每个进程的虚拟内存空间扩展为2GB。对驱动程序影响比较大的有Pointer和Share Memory,这一点在Driver与OS接口部分对指针和内存的保护方法中可以看到。对OAL影响比较大的有,系统Boot Process的改变。
另外,6.0上废除了Full Kernel Mode的不合理设计,将Kernel Mode和User Mode进行了细分。由此带来了本文中要讨论的问题,就是如何在User Mode下操作Kernel Mode下的内存空间。
大家一定想到了,简单的在User Mode下访问Kernel Mode下才有权限访问的内存肯定是行不通,可以间接的通过Kernel下的Driver或者其它与Kernel中的代码进行通信的方法来访问。
本文先讨论一下第一种方法实现中要解决的问题,最后会将主要代码实现粘贴出来。
二.需要解决的几个问题
前面已经提到主要的实现思路,需要解决的问题是如何加载该驱动,以及如何保证Driver运行在Kernel Mode下,以及如何将Driver和Exe组合到一起。
1.用户模式下Driver的加载
为了方便实现User Mode下Driver的动态加载,将这支在User Mode和Kernel Mode下做转换的Driver做成流驱动。
User Mode下加载Driver,只需两个步骤:首先将Driver拷贝到对象存储下的Windows目录下,然后调用Device Manager的API ActivateDevice()来实现动态的加载。
函数ActivateDevice()的使用非常简单,其声明如下:
This function loads a device driver. For additional functionality, use the ActivateDeviceEx function. HANDLE ActivateDevice(
LPCWSTR lpszDevKey,
DWORD dwClientInfo
);
ParameterslpszDevKey [in] Pointer to a string that identifies the location under the HKEY_LOCAL_MACHINE registry subtree where the Driver registry subkey for the device resides. A driver registry subkey contains the dynamic-link library (DLL) name, device prefix, friendly name, and other device information. dwClientInfo [in] Data to store in the Active registry subkey for the device in the ClientInfo registry entry. The registry path to the Active registry subkey for the device is passed in as the context parameter to the device's XXX_Init (Device Manager) function. After the value in dwClientInfo is stored in the registry under HKEY_LOCAL_MACHINE/Drivers/Active, the Device Manager calls XXX_Init. Devload.h defines DEVLOAD_CLIENTINFO_VALNAME and DEVLOAD_CLIENTINFO_VALTYPE to facilitate access to the ClientInfo key. |
可以看到,第一个参数用来指定Driver的注册表路径,而第二个参数用来写入到Active Key下,如果不需要写入的话,可以置为NULL。
2.如何保证加载的Driver处于Kernel Mode下
6.0下引入了Group的概念,通过注册表可以去定义一个Group,一个简单的Group定义如下:
[HKEY_LOCAL_MACHINE/Drivers/ProcGroup_0002] "ProcName"="servicesd.exe" "ProcVolPrefix"="$services" "ProcTimeout"=dword:20000
[HKEY_LOCAL_MACHINE/Drivers/ProcGroup_0003] "ProcName"="udevice.exe" "ProcVolPrefix"="$udevice" |
其实,简单点理解Group就是将Driver的加载方式进行细分,方便不同的Driver使用不同的系统组件进行加载。
Driver的注册表项可以用来指定加载自己的Group,如下:
[HKEY_LOCAL_MACHINE/Drivers/BuiltIn/Ethman] "Prefix"="ETM" "Dll"="ethman.dll" "Index"=dword:1 ; WZCSVC must be started before ethman "Order"=dword:2A ; Flags==12 is DEVFLAGS_LOADLIBRARY and DEVFLAGS_LOAD_AS_USERPROC "Flags"=dword:12 "UserProcGroup"=dword:3 ; // default to group 3
[HKEY_LOCAL_MACHINE/Drivers/BuiltIn/SIP] "Prefix"="SIP" "Dll"="softkb.DLL" "Order"=dword:1 "Index"=dword:0 ;Flags==10 is DEVFLAGS_LOAD_AS_USERPROC "Flags"=dword:10 "UserProcGroup"=dword:3 ; // default to group 3 |
对于指定通过某个Group进行加载的Driver,系统中进行加载的时候会引入Reflect机制。该机制主要用来对检察注册表项中的IoLen和IoBase值,当检察到Driver中访问了不在IoLen和IoBase指定区域的物理内存时,将会出现系统异常。
对于那些没有指定使用Group进行加载的驱动,WinCE6.0中将其加载到Kernel Mode下。也就是说,它们具有了访问整个4GB空间的权限。
要保证User Mode下加载的Driver处于Kernel Mode,只需要在注册表中不去指定User Group就可以了。
3.如何将Driver DLL和Exe 做成一个文件
之所以将Driver和EXE组合成一个文件,是为了用户使用的方便。想象一下,如果不把两者做成一个文件的话,一个简单的访问物理内存的应用程序就变成了两个文件,那是多么不美观的事情。
其实实现将Driver DLL和EXE做成一个文件有两个方法。方法一,由于DLL和EXE都是PE结构的,可以使用网上的加壳工具将其组合成一个PE文件,而在运行的时候自动去壳即可。方法二,将DLL中的信息提取出来放到EXE的Data Section,然后在运行的时候,将这些数据重新组合成一个DLL。
这里我采用了第二种方法来实现组合Driver和EXE文件。
三.代码实现
1.将Driver注册表的操作简化
操作过CE下注册表的兄弟们都知道,微软设计的注册表非常简单,可是操作API实在是不那么友好。
我这里使用了PB6.0源文件PUBLIC/WCESHELLFE/OAK/CTLPNL/CPLMAIN/cplmacro.h中的类CReg来实现对Driver注册表项的读写动作。
该注册表类主要封装了注册表的Open/Read/Write API,为用户提供了一种更加友好的注册表操作接口。
该类的定义和实现如下:
class CReg { private: HKEY m_hKey; int m_Index; LPBYTE m_lpbValue; // last value read, if any
public: BOOL Create(HKEY hkRoot, LPCTSTR pszKey) { DWORD dwDisp; return ERROR_SUCCESS==RegCreateKeyEx(hkRoot, pszKey, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &m_hKey, &dwDisp); }
BOOL Open(HKEY hkRoot, LPCTSTR pszKey, REGSAM sam=KEY_READ) { return ERROR_SUCCESS==RegOpenKeyEx(hkRoot, pszKey, 0, sam, &m_hKey); }
CReg(HKEY hkRoot, LPCTSTR pszKey) { m_hKey = NULL; m_Index = 0; m_lpbValue = NULL; Open(hkRoot, pszKey); }
CReg() { m_hKey = NULL; m_Index = 0; m_lpbValue = NULL; }
~CReg() { if(m_hKey) RegCloseKey(m_hKey); MyFree(m_lpbValue); }
void Reset() { if(m_hKey) RegCloseKey(m_hKey); MyFree(m_lpbValue); m_hKey = NULL; m_Index = 0; m_lpbValue = NULL; }
operator HKEY() { return m_hKey; }
BOOL IsOK(void) { return m_hKey!=NULL; }
BOOL EnumKey(LPTSTR psz, DWORD dwLen) { if(!m_hKey) return FALSE; return ERROR_SUCCESS==RegEnumKeyEx(m_hKey, m_Index++, psz, &dwLen, NULL, NULL, NULL, NULL); }
BOOL EnumValue(LPTSTR pszName, DWORD dwLenName, LPTSTR pszValue, DWORD dwLenValue) { DWORD dwType; if(!m_hKey) return FALSE; dwLenValue *= sizeof(TCHAR); // convert length in chars to bytes return ERROR_SUCCESS==RegEnumValue(m_hKey, m_Index++, pszName, &dwLenName, NULL, &dwType, (LPBYTE)pszValue, &dwLenValue); }
BOOL ValueSZ(LPCTSTR szName, LPTSTR szValue, DWORD dwLen) { if(!m_hKey) return FALSE; dwLen *= sizeof(TCHAR); // convert length in chars to bytes return ERROR_SUCCESS==RegQueryValueEx(m_hKey, szName, NULL, NULL, (LPBYTE)szValue, &dwLen); }
DWORD ValueBinary(LPCTSTR szName, LPBYTE lpbValue, DWORD dwLen) { if(!m_hKey) return FALSE; DWORD dwLenWant = dwLen; if(ERROR_SUCCESS==RegQueryValueEx(m_hKey, szName, NULL, NULL, lpbValue, &dwLen)) return dwLen; else return 0; }
LPCTSTR ValueSZ(LPCTSTR szName);
LPBYTE ValueBinary(LPCTSTR szName) { return (LPBYTE)ValueSZ(szName); }
DWORD ValueDW(LPCTSTR szName, DWORD dwDefault=0) { if(!m_hKey) return FALSE; DWORD dwValue = dwDefault; DWORD dwLen = sizeof(DWORD); RegQueryValueEx(m_hKey, szName, NULL, NULL, (LPBYTE)&dwValue, &dwLen); return dwValue; }
BOOL SetSZ(LPCTSTR szName, LPCTSTR szValue, DWORD dwLen) { //Prefix if(!m_hKey) return FALSE; // return ERROR_SUCCESS==RegSetValueEx(m_hKey, szName, 0, REG_SZ, (LPBYTE)szValue, sizeof(TCHAR)*dwLen); }
BOOL SetSZ(LPCTSTR szName, LPCTSTR szValue) { return SetSZ(szName, szValue, 1+lstrlen(szValue)); }
BOOL SetDW(LPCTSTR szName, DWORD dwValue) { //Prefix if(!m_hKey) return FALSE; // return ERROR_SUCCESS==RegSetValueEx(m_hKey, szName, 0, REG_DWORD, (LPBYTE)&dwValue, sizeof(DWORD)); }
BOOL SetBinary(LPCTSTR szName, LPBYTE lpbValue, DWORD dwLen) { //Prefix if(!m_hKey) return FALSE; // return ERROR_SUCCESS==RegSetValueEx(m_hKey, szName, 0, REG_BINARY, lpbValue, dwLen); }
BOOL SetMultiSZ(LPCTSTR szName, LPCTSTR lpszValue, DWORD dwLen) { return ERROR_SUCCESS==RegSetValueEx(m_hKey, szName, 0, REG_MULTI_SZ, (LPBYTE)lpszValue, sizeof(TCHAR)*dwLen); }
BOOL DeleteValue(LPCTSTR szName) { //Prefix if(!m_hKey) return FALSE; // return ERROR_SUCCESS==RegDeleteValue(m_hKey, szName); }
BOOL DeleteKey(LPCTSTR szName) { if(!m_hKey) return FALSE; return ERROR_SUCCESS==RegDeleteKey(m_hKey, szName); } }; |
具体的使用方法可参照后面代码中LoadMemDrv()的实现。
2.Driver和DLL合并与解压
合并方法很简单。首先,我将DLL中的每个字节的数据提取出来组合成一个数组,然后在AP中引用该数组。
解压的时候,直接将该数组组合成Driver的DLL就行了,如下:
// 从静态变量区提取MEM_DRV_NAME驱动的内容,并将其组合成为一个Driver的dll { HANDLE hTempFile = INVALID_HANDLE_VALUE; DWORD dwBytesReturned = 0; TCHAR szFileName[MAX_PATH/2] = {0,};
wsprintf(szFileName, L"%s%s", TEXT("//"), MEM_DRV_NAME);
hTempFile = CreateFile( szFileName, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_WRITE|FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
if (INVALID_HANDLE_VALUE == hTempFile) { LogMessage(TEXT("[ERR] Faild to create file. File name %s"), szFileName); } else { // DllFile就是DLL变量数组的名字 // 这里将DLL的内容写入到前面创建的文件L"MyMemoryDrv.dll"中 if (!WriteFile(hTempFile, DllFile, sizeof(DllFile), &dwBytesReturned, NULL)) { LogMessage(TEXT("[ERR] Faild to write file. Error code 0x%x"), GetLastError()); } else { LogMessage(TEXT("Create driver %s successfully"), szFileName); }
CloseHandle(hTempFile);
DeleteFile(MEM_DRV_DST_PATH);
//if (!CopyFile(szFileName, L"//me.dat", 0)) if (!CopyFile(szFileName, MEM_DRV_DST_PATH, FALSE)) { LogMessage(L"[ERR] Copy memory driver from %s to %s failed, Error code 0x%x!", szFileName, MEM_DRV_DST_PATH, GetLastError()); } } } |
3.Driver的实现
由于该Driver的功能仅仅是在User Mode和Kernel Mode下的内存之间做转换,所以只需要简单的实现一下DeviceIoControl就可以了,其它的流接口除了Open和Init直接为空就行了。
如下:
/* * make use of MEM_IOControl to control memory address space conversion * Para: * pInBuf: physical or virtual memory address * nInBufSize: 4 * pOutBuf: user buffer used to store data * nOutBufSize: size of data the user wanted in bytes [note********************] */ DWORD MEM_IOControl(DWORD Handle, DWORD dwIoControlCode, PBYTE pInBuf, DWORD nInBufSize, PBYTE pOutBuf, DWORD nOutBufSize, PDWORD pBytesReturned) { DWORD bRetVal = ERROR_SUCCESS; PBYTE pMemBuffer = NULL;
NKDbgPrintfW(L"MEM_IOControl(). Handle 0x%x, Code 0x%x, pInBuf 0x%8x, InSize 0x%x, OutSize 0x%x/r/n", Handle, dwIoControlCode, *(DWORD *)pInBuf, nInBufSize, nOutBufSize);
switch(dwIoControlCode) { case IOCTL_MEM_GET_PHYSICAL_RAM: NKDbgPrintfW(TEXT("IOCTL_MEM_GET_PHYSICAL_RAM/r/n")); break; case IOCTL_MEM_GET_VIRTUAL_RAM: NKDbgPrintfW(TEXT("IOCTL_MEM_GET_VIRTUAL_RAM/r/n")); break; default: NKDbgPrintfW(TEXT("**UNKNOWN**/r/n")); break; }
switch(dwIoControlCode) { case IOCTL_MEM_GET_PHYSICAL_RAM: { do { if (pInBuf == NULL || nInBufSize != sizeof(DWORD) || pOutBuf == NULL || nOutBufSize == 0) {
NKDbgPrintfW((_T("MEM_IOControl: IOCTL_MEM_GET_PHYSICAL_RAM - invalid paramter/n/r"))); bRetVal = ERROR_INVALID_PARAMETER; break; }
pMemBuffer = (PBYTE)VirtualAlloc(NULL, nOutBufSize, MEM_RESERVE, PAGE_NOACCESS);
if (NULL != pMemBuffer) {
if (!VirtualCopy((void *)pMemBuffer, (void *)((*(DWORD *)pInBuf)>>8), nOutBufSize, PAGE_READWRITE | PAGE_NOCACHE | PAGE_PHYSICAL)) { NKDbgPrintfW((_T("[MEMDRV] MEM_IOControl() : pMemBuffer VirtualCopy() Failed /n/r"))); bRetVal = ERROR_INVALID_PARAMETER; } else {
__try { NKDbgPrintfW(L"Physical Add: 0x%8x, Virtual Add: 0x%8x/r/n", *(DWORD *)pInBuf, *(DWORD*)pMemBuffer); memcpy(pOutBuf, pMemBuffer, nOutBufSize); } __except(EXCEPTION_EXECUTE_HANDLER) { NKDbgPrintfW((L"[ERR] Throw out exception in MEMDRV: MEM_IOControl()")); bRetVal = ERROR_INVALID_PARAMETER; } }
VirtualFree(pMemBuffer, 0, MEM_RELEASE); } else { NKDbgPrintfW((_T("[MEMDRV] MEM_IOControl() : pMemBuffer VirtualCopy() Failed /n/r"))); bRetVal = ERROR_INVALID_PARAMETER; } } while(0);
} break; case IOCTL_MEM_GET_VIRTUAL_RAM: { NKDbgPrintfW(TEXT("IOCTL_MEM_GET_VIRTUAL_RAM/r/n")); __try { // copy data from pInBuf to pOutBuf memcpy(pOutBuf, &pInBuf, nOutBufSize); } __except(EXCEPTION_EXECUTE_HANDLER) { NKDbgPrintfW((L"[ERR] Throw out exception in MEMDRV: MEM_IOControl()")); bRetVal = ERROR_INVALID_PARAMETER; } } break; default: NKDbgPrintfW(TEXT("**UNKNOWN**/r/n")); bRetVal = ERROR_INVALID_PARAMETER; break; }
return (ERROR_SUCCESS == bRetVal); } |
4.Driver的加载
Driver的加载包括两个过程。首先将Driver相关的注册表项写入到注册表中,然后调用API ActivateDevice()来实现动态的加载。
代码如下:
/*
[HKEY_LOCAL_MACHINE/Drivers/BuiltIn/MEM] "Dll"="MyMemoryDrv.dll" "Prefix"="MEM" "Index"=dword:1 "Order"=dword:0 "FriendlyName"="MEM driver"
*/ #define MEM_DRV_NAME L"MEM1:" bool LoadMemDrv(void) { BOOL bRetVal = false;
// Step1: modify the registry class CReg MemDrvReg;
bRetVal = MemDrvReg.Create(HKEY_LOCAL_MACHINE, MEM_DRV_PATH); bRetVal= MemDrvReg.SetSZ(L"Dll", L"MyMemoryDrv.dll");//, sizeof(L"MyMemoryDrv.dll")/sizeof(TCHAR)); MemDrvReg.SetSZ(L"Prefix", L"MEM");//, sizeof(L"MEM")/sizeof(TCHAR)); MemDrvReg.SetDW(L"Order", 0); MemDrvReg.SetDW(L"Index", 1); MemDrvReg.SetDW(L"Index", 1); MemDrvReg.SetSZ(L"FriendlyName", L"MEM driver");//, sizeof(L"MEM driver")/sizeof(TCHAR));
// Step2: load driver unsing device manager hActiveMemDrv = INVALID_HANDLE_VALUE;
hActiveMemDrv = ActivateDevice(MEM_DRV_PATH, 0); if (INVALID_HANDLE_VALUE == hActiveMemDrv) { LogMessage(L"[ERR]Load driver %s failed", MEM_DRV_FULL_PATH); goto EXIT; }
// Step3: Open stream driver hFile = INVALID_HANDLE_VALUE; hFile = CreateFile(MEM_DRV_NAME, GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (INVALID_HANDLE_VALUE == hFile) { LogMessage(L"[ERR] Open stream driver %s failed. Error code 0x%8x", MEM_DRV_NAME, GetLastError()); goto EXIT; }
bIsDrvLoad = true; bRetVal = TRUE;
EXIT: return (bRetVal == TRUE); } |
附:
具体的实现代码可以到我的资源中下载。