驱动里执行应用层代码之KeUserModeCallBack(WOW64是由三个动态库wow64.dll wow64win.dll wow64cpu.dll来实现)
在驱动层(ring0)里执行应用层(ring3)代码,这是个老生常谈的技术,而且方法也挺多。
这种技术的本质:其实就是想方设法在驱动层里把应用层代码弄到应用层去执行。
比如在APC异步调用中,KeInsertQueueApc,KeInitializeApc等函数中可设置一个在ring3层执行一个回调函数,这样就可以回到应用层去执行代码了。
再比如在驱动中查找某个进程的一个线程,然后挂起它,把他的EIP指向需要执行的一段代码(把驱动层需要注入的这段代码叫ShellCodde),
执行完之后再回到线程原来的地方继续执行。
或者HOOK某些经常被调用的系统函数,比如NtCreateThread等,然后把ShellCode注入到当前进程去执行。
方法不下七八种之多。
无非就是在驱动层里主动把ShellCode注入到某个进程执行,或者被动的当某个进程进入驱动之后,然后调用ShellCode。
虽然办法挺多,但是由于出现得比较早,而且又不是微软提倡的,也没得到微软的支持,所以兼容性很差。
往往大部分办法只对WINXP支持得挺好,到了win7之后就会出现各种各样的问题,尤其是 64位的 win7系统,能用的办法就非常少了。
我没试过上边提到的办法能不能在64位win7是否成功,一开始接触这个问题的时候,使用的是 KeUserModeCallBack。
使用它是因为这函数虽然没被微软文档化,但是过了10多年,它的接口都不曾变化过,而且被windows内部大量使用。
KeUserModeCallback函数原型如下:
NTSTATUS KeUserModeCallback (
IN ULONG ApiNumber,
IN PVOID InputBuffer,
IN ULONG InputLength,
OUT PVOID *OutputBuffer,
IN PULONG OutputLength
);
在KeUserModeCallback里,调用 KiServiceExit进入到ring3,在应用层接着调用KiUserCallbackDispatcher,
这个函数里,会通过传递的ApiNumber,计算出应用层回调函数地址,然后调用这个回调函数.
计算公式是 FuncAddr= KernelCallbackTable + ApiNumber*sizeof(PVOID); //同样适用64位系统.
KernelCallbackTable 存储回调函数基地址,非GUI进程KernelCallbackTable为NULL。
回调函数的第一个参数是 KeUserModeCallback的第二个参数InputBuffer, 回调函数的第二个参数是InputLength。
回调函数调用完成之后,通过触发int 2B调用KiCallbackReturn再次进入到内核,最后从 KeUserModeCallback 返回。
以下演示了如何使用这个函数的伪代码:
struct USERDATA
{
........
};
NTSTATUS WINAPI UserCallback(PVOID Arguments, ULONG ArgumentLength)
{
USERDATA* user = (USERDATA*)Arguments;
....//实现在应用层调用的代码
return STATUS_SUCCESS;
}
void UserCallbackEnd(){}
//分配内存,KeUserModeCallback 第一个参数是 ULONG, 所以 64位系统的分配策略从基址开始寻找 4G范围内的空闲空间
static NTSTATUS getProcessMemory(HANDLE proc_handle,PVOID baseAddr, PVOID* ppMem, SIZE_T* pSize)
{
NTSTATUS status = STATUS_UNSUCCESSFUL;
#ifdef _WIN64
const ULONG COUNT = 1000; const ULONG SepSize = 1024 * 1024 * 3 / 2; const ULONG_PTR Base = 1024 * 1024 * 50;
ULONG i;
for (i = 0; i < COUNT; ++i){
ULONG_PTR pMem = (ULONG_PTR)baseAddr + Base + i*SepSize;
SIZE_T size = *pSize;
status = ZwAllocateVirtualMemory(proc_handle, (PVOID*)&pMem, 0, &size, MEM_COMMIT | MEM_RESERVE , PAGE_EXECUTE_READWRITE);
if (NT_SUCCESS(status)){
*pSize = size;
*ppMem = (PVOID)pMem;
break;
}
}
#else
status = ZwAllocateVirtualMemory(proc_handle, ppMem, 0, pSize, MEM_COMMIT | MEM_RESERVE | MEM_TOP_DOWN, PAGE_EXECUTE_READWRITE);
#endif
return status;
}
// KeUserModeCallback一定是在用户进程线程上下文环境中才能执行成功,为了保证KernelCallbackTable不为空,必须是加载user32.dll的GUI进程。
void CallKeUserModeCallback()
{
PVOID pMem = NULL;
PROCESS_BASIC_INFORMATION pbi;
PVOID KernelCallbackTable;
ULONG ApiNumber;
HANDLE proc_handle = NtCurrentProcess();
//
ZwQueryInformationProcess( proc_handle, ProcessBasicInformation, &pbi, sizeof(PROCESS_BASIC_INFORMATION), NULL);
KernelCallbackTable = pbi.PebBaseAddress->KernelCallbackTable; //获得用户层回调函数基地址
////// 为当前进程分配一段用户空间内存,目的是为了把回调函数UserCallback, 以及回调函数需要用到的参数, 复制到用户空间内存中。
////// ApiNumber的计算办法 ApiNumber = (((ULONG_PTR)pMem - (ULONG_PTR)KernelCallbackTable) / sizeof(ULONG_PTR));
//////因为ApiNumber是ULONG类型, 可以看出,对于64位系统pMem和KernelCallbackTable的差值不能超过4G范围,否则计算出的ApiNumber就是错误的。
getProcessMemory(proc_handle, KernelCallbackTable, &pMem, &size);
ApiNumber = (((ULONG_PTR)pMem - (ULONG_PTR)KernelCallbackTable) / sizeof(ULONG_PTR));
PVOID ShellCodeAddr = (PVOID)((ULONG_PTR)pMem + sizeof(ULONG_PTR));
ULONG ShellCodeSize = (ULONG_PTR)UserCallbackEnd - (ULONG_PTR)UserCallback;
*(ULONG_PTR*)pMem = (ULONG_PTR)UserCallback; /// 等同 KernelCallbackTable[ApiNumber] = UserCallback;
USERDATA ud; //初始化用户栈结构,这个结构会被传递给UserCallback函数
....
PVOID OutBuffer; ULONG OutLen;
////调用KeUserModeCallback, 直到用户层的UserCallback函数调用完成之后才返回。
KeUserModeCallback( ApiNumber, &ud, sizeof(USERDATA), &OutBuffer, &OutLen);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
调用 KeUserModeCallback有个最大的限制,
他必须在 用户GUI进程的线程上下文环境中被调用才能成功,
简单的说吧,如果你在DriverEntry中或在PsCreateSystemThread 创建的线程中调用 KeUserModeCallback ,会失败。
因为他们都没有用户堆栈空间。而且为了保证KernelCallbackTable不为空,进程必须是调用user32.dll的GUI进程。
windows大部分都是调用user32.dll的进程,这个条件不难满足。
关键是如何进入到某个进程的执行上下文环境中。
一开始想到的就是 PsSetCreateProcessNotifyRoutine 和 PsSetCreateThreadNotifyRoutine。
这两个函数设置的回调函数,确实能进入到被创建的进程上下文环境中,但是在win7下,
KeUserModeCallback调用更加严格,他只能运行在 PASSIVE_LEVEL级别,同时是 APC Enables的状态。
否则就会蓝屏,条件可参考
http://thisissecurity.net/2014/04/08/how-to-run-userland-code-from-the-kernel-on-windows/
上边有KeUserModeCallback函数的详细阐述。
得另想办法来进入用户进程上下文环境,一个比较通用,而且几乎所有用户进程都会进入的就是文件过滤驱动。
在文件过滤驱动的IRP_MJ_CREATE派遣函数中,能确保处于PASSIVE_LEVEL和APC Enables状态。
可以直接使用minifilter驱动。如下:
PFLT_FILTER gFilterHandle;
FLT_PREOP_CALLBACK_STATUS
NPPreCreate(
__inout PFLT_CALLBACK_DATA Data,
__in PCFLT_RELATED_OBJECTS FltObjects,
__deref_out_opt PVOID *CompletionContext
)
{
FLT_PREOP_CALLBACK_STATUS retStatus = FLT_PREOP_SUCCESS_NO_CALLBACK;
NTSTATUS status;
/////已经进入到某个进程的上下文执行环境中
cbk_execute(); ///////////////////// 在这里调用KeUserModeCallback函数。
////
return retStatus;
}
// operation registration
const FLT_OPERATION_REGISTRATION Callbacks[] = {
{ IRP_MJ_CREATE,
0,
NPPreCreate,
0},
{ IRP_MJ_OPERATION_END }
};
// This defines what we want to filter with FltMgr
const FLT_REGISTRATION FilterRegistration = {
sizeof(FLT_REGISTRATION), // Size
FLT_REGISTRATION_VERSION, // Version
0, // Flags
NULL, // Context
Callbacks, // Operation callbacks
NPUnload, // MiniFilterUnload
NULL, // InstanceSetup
NULL, // InstanceQueryTeardown
NULL, // InstanceTeardownStart
NULL, // InstanceTeardownComplete
NULL, // GenerateFileName
NULL, // GenerateDestinationFileName
NULL // NormalizeNameComponent
};
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING reg)
{
status = FltRegisterFilter(DriverObject, &FilterRegistration, &gFilterHandle );
if (NT_SUCCESS(status)){
///
status = FltStartFiltering( gFilterHandle);
if (!NT_SUCCESS(status)){
FltUnregisterFilter(gFilterHandle);
return status;
}
}
。。。。
return status;
}
最后说说这种技术有什么作用。
其实如果从一般的角度去理解,非常简单的就可以在应用层执行一段代码,何必那么麻烦非要从驱动里执行一段用户层代码。
所以好像是没什么用处。确实,一般在开发驱动程序中,都是开发配套的应用层程序跟驱动通讯来处理事务。
既然有了配套的应用层程序,就没必要再驱动中执行用户层代码了。
可是从另一个角度去想,驱动中执行一段用户层代码,没有看得见的程序(其实是寄宿在某个系统进程比如explorer,winlogon等)参与其中。
用户就很难发现有什么玩意执行过。
所以这个可以为某些木马或者恶意软件提供一个方便之门。比如像360tray.exe这种顽固的连ARK工具都无法结束的进程,
可以在驱动里把一个DLL注入到 360tray.exe里,然后再DLL里调用ExitProcess把自己给束掉
(这个办法也许有效,也许也没效,有兴趣的可以试试)。
也为某些人开发了驱动和DLL,就是不想开发EXE应用程序,而且也不想使用svchost来启动这个DLL,提供了一个另外的途径。
源代码下载地址:
http://download.csdn.net/detail/fanxiushu/7681759
补充:
以上讨论的都是执行原生程序的情况,也就是32位系统执行32位程序,64位系统执行64位程序。
但是在64位系统有个特殊的情况,即64位系统执行32位程序。
现在要补充说明的,就是如何在 win7 64位系统中,在驱动中调用KeUserModCallback函数,把代码注入到 32位进程去执行。
首先简单说说32位进程如何在64位系统中运行。
微软在用户模式实现了一个叫WOW64的子系统,用来为32位进程提供32位的模拟环境。
WOW64作为ntdll.dll和内核之间的一个层,它起到了欺上瞒下的作用, 在32位进程来看,他们愉快的以为是运行在32位系统中,
但是对内核来说,他们却以为上边运行的是64位进程。
WOW64是由三个动态库来实现,
wow64.dll 实现核心部分
wow64win.dll 实现一些我不知道的功能,
wow64cpu.dll 实现CPU在 32位和64位模式之间转换。
接着再看看KeUserModeCallback在32位进程和64位进程的处理有何不同,其实并没有本质的区别。
KeUserModeCallback返回到应用层之后,都会调用ntdll里边的KiUserCallbackDispatcher 函数,然后KiUserCallbackDispatcher根据ApiNumber
找到我们设置的回调函数UserCallback。因此,不管是32位进程和64位进程,我们设置的 UserCallback 函数都会被调用。
但是有点必须注意,那就是 UserCallback函数 不管是32位进程中,还是64位进程中,他都处于CPU是64位的执行环境中。
对于64位进程,这没有任何问题,所以可以获得某个模块的API函数,然后执行之。
但是对于32位进程,这就是大问题了,32位进程处于 32位CPU模式,所以32位进程中,我们无法调用跟32位模块相关的任何函数,
这样做的结果就只有一个,32位进程崩溃,然后KeUserModeCallback永远无法返回。
那如果我们想办法让我们的UserCallback函数进入到 CPU 32位模式,不就可以执行任何函数了吗?
事实确实如此,可是关键如何进入呢? 这就是问题所在。
因为我并不熟悉WOW64的CPU模式切换过程(或者说我并不熟悉CPU的模式切换),所以想自己写代码把UserCallback切换到32位模式,是无能为力了。
好在通过查看WOW64的三个动态库的导出函数,发现wow64.dll中有个函数 Wow64KiUserCallbackDispatcher,
跟ntdll.dll导出的 KiUserCallbackDispatcher是那么相近,就多了一个Wow64前缀。
猜想应该是在32位模式中执行回调函数。通过查找各种资料,证实了我的猜测。此函数声明如下,因为不是文档化的函数,未来极有可能被微软修改:
void NTAPI Wow64KiUserCallbackDispatcher( OUT PCONTEXT Contex, ULONG Wow64_APiNumber, PVOID Arguments, ULONG ArgumentLength);
最后两个参数就是系统传递给我们的UserCallback 函数的参数,也是 KeUserModeCallback函数的第二个和第三个参数。
第一个参数是个CONTEXT,就是线程上下文环境,测试发现,我们只需分配一块CONTEXT内存传递进去,无需填写任何参数。
第二个参数Wow64_APiNumber 是干嘛的呢? 他的作用跟 ApiNumber一样,只是他是在32位环境中,
因为32位进程都两个进程环境块,一个 PEB64,一个 PEB32。
PEB32中同样有个 KernelCallbackTable 基址,他是所有32位回调函数的基地址。
PEB32如何获得呢? 使用 PPEB32 PsGetProcessWow64Process(PEPROCESS ep); 又是一个未文档化的函数。
我们首先要做个32位环境的UserCallback32,然后写到32位进程空闲内存中,然后计算 Wow64_ApiNumber,
Wow64_ApiNumber = ((ULONG)pMem32 - (ULONG)KernelCallbackTable32) / sizeof(ULONG);
至于如何制作32位UserCallback32,其实就是如何制作ShellCode,网上有介绍如何制作。
简单的说就是编译一个包含UserCallback32函数的程序,然后把这个函数当成数组写到某个文件中,然后就是纯数组形式的ShellCode了。
有了这些准备,就可以在我们的UserCallback64中调用 Wow64KiUserCallbackDispatcher , 这样WOW64子系统自动帮我们切换到 32位环境,
然后执行我们的32位的Usercallback32函数, 然后返回,最后回到KeUserModeCallback 。
看起来的伪代码如下:
struct USERDATA
{
WOW64Func Wow64KiUserCallbackDispatcher;
PCONTEXT pContext;
ULONG Wow64_ApiNumber;
............
其他参数,提供给32环境的UserCallback32使用
};
NTSTATUS WINAPI UserCallback64 (PVOID Arguments, ULONG ArgumentLength)
{
USERDATA* user = (USERDATA*)Arguments;
///此函数帮我们转换到32位模式,并且执行32位环境中的 回调函数
user-> Wow64KiUserCallbackDispatcher( user->pContext, user->Wow64_ApiNumber, Arguments, ArgumentLength );
.....
return STATUS_SUCCESS;
}
void UserCallbackEnd(){}
///下边的函数,需要在32位编译环境中制作出 ShellCode数组,因为64编译环境无法编译出32位代码来。
NTSTATUS WINAPI UserCallback32 (PVOID Arguments, ULONG ArgumentLength)
{
USERDATA* user = (USERDATA*)Arguments;
........执行32位环境的代码
return STATUS_SUCCESS;
}
void CallKeUserModeCallbackWow64()
{
获取peb64; 获取 KernelCallbackTable64
获取peb32;获取 KernelCallbackTable32
给当前32位进程分配内存pMem64,用来存储 UserCallback64代码,CONTEXT等
给当前进程分配内存pMem32, 用来存储UserCallback32及其一些参数等
计算ApiNumber64,提供给 KeUserModeCallback用,
计算Wow64_ApiNumb32,提供给 Wow64KiUserCallbackDispatcher用
遍历 peb64的所有模块,找到wow64.dll,并且获取导出函数Wow64KiUserCallbackDispatcher地址,
(至于如何获得这些信息,可参考我提供的愿代码,除了WOW64部分未提供外,其他都是全的)
遍历peb32的所有模块,找到需要执行某个API函数的模块,然后获取此API函数在32位环境中的地址。
USERDATA user;
user.Wow64KiUserCallbackDispatcher = 获得的Wow64KiUserCallbackDispatcher地址,
user.pContext = pMem64指向的其中一部分内存
user.Wow64_ApiNumber=Wow64_ApiNumber32;
.....初始化其他参数
这个user 最终会被KeUserModeCallback传递到 我们在32位环境中设置的UserCallback32函数里。
KeUserModeCallback( ApiNumber64 , &user, sizeof(USERDATA), &OutBuffer, &OutLen);
}
挺麻烦的WOW64处理吧。
就为了执行那么一小段代码,搞的这么复杂,所以没事还是别做这么逆天的玩意。
参考:http://blog.csdn.net/fanxiushu/article/details/38141219
驱动里执行应用层代码之KeUserModeCallBack(WOW64是由三个动态库wow64.dll wow64win.dll wow64cpu.dll来实现)的更多相关文章
- java 里执行javascript代码
import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; ScriptEngineManager sem = ...
- MongoDB:利用官方驱动改装为EF代码风格的MongoDB.Repository框架 三
本次改动的主要内容是实现MongoDB.Repository在MongoDB中建立索引. 建立索引主要使用MongoDB的官方驱动中EnsureIndex方法. 在MongoDB.Repository ...
- SQL里执行CLR c#代码
这里只说一个重点: 1.直接在sql里执行clr代码的时候,sql还是会报错 说没有启用 clr 执行以下代码才会起作用 EXEC sp_configure 'clr enabled', 1; RE ...
- 在 Linux/windows下 命令行中使用和执行 PHP 代码[交互式php]
[注释]在ubuntu下,升级php到7.1版本,虽然提示的是Interactive mode enabled, 但实际上可以直接书写命令,和interactive shell效果一样. 一:wind ...
- 静态库动态库的编译、链接, binutils工具集, 代码段\数据段\bss段解释
#1. 如何使用静态库 制作静态库 (1)gcc *.c -c -I../include得到o文件 (2) ar rcs libMyTest.a *.o 将所有.o文件打包为静态库,r将文件插入静态库 ...
- 把纯C的动态库代码改造成C++版的
近期想把一份纯C的跨Win/Linux的动态库工程代码改成支持C++编译器,这样用C++写起代码来比较顺手.要点是保证动态库的ABI一致性,既导出接口不能改变. 主要的改动有: 1.把.c后缀名换成. ...
- jsp页面:js方法里嵌套java代码(是操作数据库的),如果这个js 方法没被调用,当jsp页面被解析的时候,不管这个js方法有没有被调用这段java代码都会被执行?
jsp页面:js方法里嵌套java代码(是操作数据库的),如果这个js 方法没被调用,当jsp页面被解析的时候,不管这个js方法有没有被调用这段java代码都会被执行? 因为在解析时最新解析的就是JA ...
- 2、CC2541芯片中级教程-OSAL操作系统(进一步了解-OLED && 普通按键和5方向按键-中断!!!)这个系统驱动层和应用层不一样~
本文根据一周CC2541笔记汇总得来—— 适合概览和知识快速索引—— 全部链接: 中级教程-OSAL操作系统\OSAL操作系统-实验01 OSAL初探 [插入]SourceInsight-工程建立方法 ...
- Minifilter微过滤框架:框架介绍以及驱动层和应用层的通讯
minifilter是sfilter后微软推出的过滤驱动框架.相比于sfilter,他更容易使用,需要程序员做的编码更简洁. 系统为minifilter专门制作了一个过滤管理器,这个管理器本身其实是一 ...
随机推荐
- tomcat绿色版及安装版修改内存大小的方法
1.对于安装版,比较方便了,直接运行tomcat6w.exe,选择Java选项卡, 在这里,可以设置初始化内存,最大内存,线程的内存大小. 初始化内存:如果机器的内存足够大,可以直接将初始化内存设置为 ...
- 必胜宅急送Web app设计背后的思考
O2O模式是餐饮业在移动消费趋势下主动拥抱互联网的方向,迎合餐饮消费者从以往经验判断为主转变为依靠移动设备.lbs.社交网络进行立体决策的过程.继App客户端之后,手机web app也逐渐成为O2O中 ...
- 【技术文档】《算法设计与分析导论》R.C.T.Lee等·第4章 分治策略
分治策略有一种“大事化小,小事化了”的境界,它的思想是将原问题分解成两个子问题,两个子问题的性质和原问题相同,因此这两个子问题可以再用分治策略求解,最终将两个子问题的解合并成原问题的解.有时,我们会有 ...
- 使用Dreamwaver cc中的SVN功能,用于传输BAE和SAE中的文件
前沿: 假期使用BAE和SAE开发应用,两个服务器都需要通过SVN提交代码,因为平时大多使用Dreamwaver,所以查了查资料,通过Subversion方便了开发. 因为网上的资料都不全,所以根据自 ...
- [PWA] 18. Clean the photo cache
We cannot let photo always keep caching new data without clean the old data. If message is not displ ...
- javascript实现Map
function Map() { this.elements = new Array(); // 获取MAP元素个数 this.size = function() { return this.elem ...
- composer之安装
最近想要学习下yii框架,所以,就看了下官网,看到了貌似比较依赖composer这个东西,然后我就安装了,但是会有问题,安装不上等等问题,不论是windows还是linux命令行安装,都是因为一个问题 ...
- Canvas画椭圆的方法
虽然标题是画椭圆,但是我们先来说说Canvas中的圆 相信大家对于Canvas画圆都不陌生 oGC.arc(400, 300, 100, 0, 2*Math.PI, false); 如上所示,直接 ...
- Linq101-Miscellaneous
using System; using System.Collections.Generic; using System.Linq; namespace Linq101 { class Miscell ...
- Ajax的工作流程简述
提到Ajax相信我们都不会陌生,不管你是前端开发还是后台数据处理的程序员,ajax的作用就像现在生活中的手机一样,无论是作用还是流程都差不多,这里我们要进行ajax操作后台数据并显示在页面上的话,首先 ...