一种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
);

Parameters

lpszDevKey

[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);

}

附:

具体的实现代码可以到我的资源中下载。

一种User Mode下访问物理内存及Kernel Space的简单实现相关推荐

  1. Linux下访问处理器硬件信息原理:图形化工具RWLinux的诞生

    大家好,这里是第五位面壁者.今天继续我们的学习之旅,同样视频会放在B站,PPT和视频录音的文字版本会在下面这些平台同步更新,欢迎各位大佬参观指导. 今天的这期内容,我给大家介绍一款我自己编写的可以在L ...

  2. Linux下访问数据库

    Linux下访问数据库 声明:本文只简单描述Linux系统下访问mysql数据库的步骤,关于连接上数据库之后的简单的对于数据库的增删改查等操作只是稍微提及,关于增删改查的语句书写,本文不再讲述. 一般 ...

  3. linux 内网共享文件夹_在Linux下访问Windows共享文件夹

    说明 以下操作以Ubuntu为例,大家可以参考. 我在Ubuntu 14.04和16.04都试过了. Windows共享文件夹 如果局域网内有一台Windows主机,将指定文件夹设为共享,就可以在局域 ...

  4. kali查看共享 linux_在Linux下访问Windows共享文件夹

    在Linux下访问Windows共享文件夹 说明 以下操作以Ubuntu为例,大家可以参考. 我在Ubuntu 14.04和16.04都试过了. Windows共享文件夹 如果局域网内有一台Windo ...

  5. linux 访问文件软件,Linux下访问文件的基本模式

    Linux下访问文件的基本模式 发表于:2013-10-09来源:IT博客大学习作者:edsionte点击数: 访问文件的操作主要是指读文件和写文件,下文简单说明内核中几种常见的访问文件的方式. 访问 ...

  6. 电脑访问不了虚拟机ftp服务器,解决win环境下访问本机虚拟机中centos7 ftp服务器的问题...

    inux搭建ftp服务器 1.安装软件: yum install vsftpd 2.修改配置文件vsftpd.conf: vim /etc/vsftpd/vsftpd.conf 把anonymous_ ...

  7. linux usb 批量传输文件,一种Linux系统下提升usb批量传输速度的方法及系统与流程...

    本发明涉及通信传输技术领域,具体地说是一种linux系统下提升usb批量传输速度的方法及系统. 背景技术: linux系统访问usb设备有两种方式:编写内核驱动模块ko和在用户空间编写程序,通过内核提 ...

  8. 一种linux平台下算法库二进制文件加密方法探讨

    最近做项目遇到一个需求,需要把我们的图像算法库提供给客户使用,为防止算法库被对方滥用和逆向破解,需要对算法库二进制文件做加密处理以及加密狗绑定,同时防止库文件被反调试跟踪.算法库加密可以借助开源软件 ...

  9. linux下挂载优盘脚本,一种linux系统下自动挂载U盘的方法与流程

    技术领域 本发明涉及服务器维护技术领域,尤其涉及一种linux系统下自动挂载U盘的方法. 背景技术: 不管是测试服务器的维护,还是用户使用的服务器,在维护.更新.使用过程中,需要使用U盘向服务器拷贝文 ...

最新文章

  1. QPainterPath 不规则提示框(二)
  2. H01-P1201-0.6B金升阳高压模块
  3. switchcase的用法
  4. STM32 PWM输出(映射)
  5. 美团脱颖而出的经验_使数据科学项目脱颖而出的6种方法
  6. SpringBoot2 集成 xxl-job任务调度中心_参数传递
  7. sql2000数据库备份文件还原到sql2005
  8. UVA - 10779 Collectors Problem
  9. win7家庭版更改桌面图标
  10. 计算机拼音字体大小设置,怎样调整字体大小
  11. 3d数字孪生智慧城市大数据可视化建设方案
  12. php试纸是干什么用,怎样做ph试纸
  13. 【Linux云计算架构:第三阶段-Linux高级运维架构】第23章—— Linux手动木马查杀过程
  14. Linux重启 Redis自动启动
  15. 总结安卓应用市场App首发活动规则
  16. XPDL与WS-BPEL的比较之五:形势与未来
  17. 数据结构的一些基本术语概念-严蔚敏老师
  18. ajax请求 下载zip压缩包
  19. word多个标题一不能显示在一页
  20. 初学Redis最清晰完整的教程

热门文章

  1. 九个著名科技公司的十位CEO的办公桌照片
  2. ie的严苛,firefox的宽容
  3. 用asp.net 2.0实现网站二级域名(转)
  4. 制作点击文字变颜色_腾讯的动态时间轴PPT火了!制作简单又有逼格,都学起来啊...
  5. 使用 UML 进行业务建模:理解业务用例与系统用例的相似和不同之处
  6. 真正简单的基于prototype的表单验证
  7. lombok插件_lombok插件,让代码更简洁
  8. python pprint_【Python】输入和输出
  9. java清除浏览器记录_java – 如何在Selenium测试中清除浏览器缓存
  10. python输入一个数组输出24进制式的时间_【翻译】《利用Python进行数据分析·第2版》第4章(下)NumPy基础:数组和矢量计算...