根据上篇文章的结尾,R3用户层越级SSDT表执行内存的读写。
这是什么意思呢?首先我们还是说一下类似OpenProcess
这个函数的调用原理,其实上篇文章也提及到,这里再重复一下。
其实调用OpenProcess函数,实际上我们是去ntdll中调用ZwOpenProcess(通过反汇编发现NtOpenprocess是同一个地址),我们只需要hookSSDT表中的NtOpenprocess函数就可以实现全局的hook OpenProcess函数,达到了保护进程的效果,也就是别的进程无法打开进程,为此我写了一个驱动去实现这个效果,配合上R3的用户交互,大家简单看下效果:
可以看到我加载驱动之后输入指定的PID,系统的NtOpenProcess函数就被hook,hook中判断如果OpenProcess打开的进程PID号等于我们输入的PID,那么返回NT_STATUS_ACCESS_DENIED,导致像OD,CE类型的工具无法附加进程,也就不能对程序进行调试了。
其实我们hook的只是SSDT表:
如果我们能从R3穿越到R0去执行NtOpenprocess函数,那么他的这个hook也就没有什么太大的意义了。
当然我们也可以再写一个驱动去UNHOOK,但是这对于一些不熟悉驱动的人来说,并不是一件友好的事情,如果有一个框架可以去实现这样的“越级”执行的话呢那是最好不过的了,我在网上的浏览中并没有得到类似的越级框架,那么只能麻烦一下,自己去写一个这样子的小框架。
这个框架开发之前,我以为一上午就写完了,实际上也就是一上午的代码量,但是我却写了一天半,这是为什么呢?因为还有一天的时间我面对的是电脑的蓝屏以及重启,不小心总是会带给我满屏蓝色,但是幸好最后我还是完成了(中途一直不想写了)。
框架有四个文件:
- R0JmpDLL.dll:这个是框架的主代码
- R0JmpDLL.lib:编写程序用的一个lib库
- R0JmpHook.h:lib库的头文件
- WkerR0JmpDriver.sys:核心驱动
框架的缺陷:只能对win7 32位使用,原因是我的DDK版本貌似只能编译win7,win8的,不支持64位是因为不想写了,其实编写上只是有两点不一样,之后会补上的。
框架的原理是,我会提供一个函数去让大家去加载核心驱动,加载的方法,也就是调用我提供给大家的dll文件里面有。加载驱动之后,我们就可以通过这个驱动去越级执行了,具体操作如下:
- 首先导入我们的头文件,这个很简单。
- 接着导入我们lib:
#pragma comment(lib,"R0JmpDLL.lib")
完成这两步之后的操作就是我们编程的思路了。
R0JMPDLL BOOL R0JmpLoadDriver(char *);
R0JMPDLL BOOL R0JmpUnLoadDriver();
R0JMPDLL HANDLE initSYS();
R0JMPDLL HANDLE R0JmpOpenProcess(HANDLE hDevice,DWORD dwDesiredAccess,DWORD PID);
R0JMPDLL BOOL R0JmpReadProcessMemory(HANDLE hDevice,HANDLE hProcess,LPCVOID lpBaseAddress,LPVOID lpBuffer,DWORD nSize,LPDWORD lpNumberOfBytesRead);
R0JMPDLL BOOL R0JmpWriteProcessMemory(HANDLE hDevice,HANDLE hProcess,LPVOID lpBaseAddress,LPVOID lpBuffer,DWORD nSize,LPDWORD lpNumberOfBytesWritten);
R0JMPDLL VOID R0JmpCloseHandle(HANDLE hDevice,HANDLE hProcess);
我给大家提供了这几个函数。
-
R0JmpLoadDriver
这个函数只有一个参数,就是完整的驱动程序名称 -
R0JmpUnLoadDriver
这个当然就是卸载驱动了 -
initSYS
这个其实实际上就是获取到驱动对象的一个句柄的,简单来说可以这么称呼,返回的就是我们在之后操作中要一种用到的hDevice - 下面的函数可以看到,和我们win32下的函数用法是一样的,就多了一个HANDLE hDevice,这个就是我们在
initSYS
中返回的句柄
简单介绍完之后,我们就可以编写了。
随便准备一个测试的被保护程序,让他有个全局变量可以变动就可以了。
我这里简单准备了一个,他的变量地址是:0x4A3600,我们目标是在他被保护的状态下对其数值进行读写。
- 加载驱动
//LoadDriver
char szFullPathname[MAX_PATH] = {0};
char szFullPath[MAX_PATH] = {0};
GetModuleFileName(NULL, szFullPathname, MAX_PATH);
strncpy(szFullPath, szFullPathname, strlen(szFullPathname)-strlen(argv[0]));
strcat(szFullPath,"WkerR0JmpDriver.sys");
R0JmpLoadDriver(szFullPath);
这个就是获取当前的路径,然后拼接我们的核心驱动,传进我们的参数驱动加载。
- 初始化驱动
//初始化驱动
HANDLE hDevcie = initSYS();
if (hDevcie == INVALID_HANDLE_VALUE)
{
printf("驱动初始化失败\n");
return 1;
}
没啥好说的,就是初始化呗。
-
OpenProcess
打开进程
//OpenProcess
int PID = -1; scanf("%d",&PID);
HANDLE oh = R0JmpOpenProcess(hDevcie,PROCESS_ALL_ACCESS,PID);
if (oh == INVALID_HANDLE_VALUE)
{
printf("无效的句柄\n",oh);
}
让用户输入进程的ID,当然自己去找也是可以的,但这里为了简单,就输入一个吧,会返回给我们一个HANDLE句柄,这个句柄就是Read和Write的句柄了,但是需要注意的是,这个句柄你如果用用户层win32下的API是无法使用的,因为你在User mode下,而这个句柄只有在Kernel mode下才能使用,我觉的应该是这个内核句柄的对象有一个字段表示的吧,我也没用windbg详细的分析。
-
ReadProcessMemory
读取进程内存
DWORD realdRead = 0;
//ReadProcessMemory
int adrnum=0;
R0JmpReadProcessMemory(hDevcie,oh,(LPCVOID)0x4A3600,&adrnum,sizeof(adrnum),&realdRead);
printf("读取到的内容:%d\n",adrnum);
可以看到基本就和win32下没有什么区别,无非就是多传了一个参数而已。
-
WriteProcessMemory
写进程内存
//WriteProcessMemory
int Newadrnum = 100;
R0JmpWriteProcessMemory(hDevcie,oh,(LPVOID)0x4A3600,&Newadrnum,sizeof(Newadrnum),&realdRead);
同样也是和win32没区别,写一个整数进去。
-
CloseHandle
关闭句柄
//CloseHandle
R0JmpCloseHandle(hDevcie,oh);
关闭我们之前通过OpenProcess打开的句柄
- 卸载驱动
//UnLoadDriver
R0JmpUnLoadDriver();
流程大致也就分为这七步,无非就是在打开和关闭之间我们可以多做点事情,还有一点也是需要注意的,不可以去并行操作,这一点很关键,我在写的时候忘记用StartIO去串行处理了,所以尽量不要并用同时读取或者写入。
看一下程序运行的效果:
可以看到Hook NtOpenprocess之后,CE无法附加,但是我们可以正常的OpenProcess进行内存的读写。
之前一直没考虑到过保护的问题,准备把之前的那个Wker_EXEDebug全面的实现R0级的内存读写,可以实现无视SSDT的HOOK的效果。
Nt的inline hook想要实现框架的难度很高,就算能实现也不是我一个人能写完的。
R0Jmp框架.zip (7.9 KB)
刚才忘记上传了