驱动里执行应用层代码之KeUserModeCallBack(WOW64是由三个动态库wow64.dll wow64win
在驱动层(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的状态。
否则就会蓝屏,条件可参考
上边有KeUserModeCallback函数的详细阐述。
得另想办法来进入用户进程上下文环境,一个比较通用,而且几乎所有用户进程都会进入的就是文件过滤驱动。
在文件过滤驱动的IRP_MJ_CREATE派遣函数中,能确保处于PASSIVE_LEVEL和APC Enables状态。
可以直接使用minifilter驱动。如下:
温馨提示: 本文由Jm博客推荐,转载请保留链接: https://www.jmwww.net/file/67398.html