当前位置:首页 > Windows程序 > 正文

驱动里执行应用层代码之KeUserModeCallBack(WOW64是由三个动态库wow64.dll wow64win

2021-03-25 Windows程序

在驱动层(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