驱动里执行应用层代码之KeUserModeCallBack,支持64位win7(包括WOW64)

您所在的位置:网站首页 ax200驱动win7 驱动里执行应用层代码之KeUserModeCallBack,支持64位win7(包括WOW64)

驱动里执行应用层代码之KeUserModeCallBack,支持64位win7(包括WOW64)

2023-03-09 03:11| 来源: 网络整理| 查看: 265

by Fanxiushu            引用或转载请注明原做者  2014-07-26windows

在驱动层(ring0)里执行应用层(ring3)代码,这是个老生常谈的技术,并且方法也挺多。数组

这种技术的本质:其实就是千方百计在驱动层里把应用层代码弄到应用层去执行。异步

好比在APC异步调用中,KeInsertQueueApc,KeInitializeApc等函数中可设置一个在ring3层执行一个回调函数,这样就能够回到应用层去执行代码了。函数

再好比在驱动中查找某个进程的一个线程,而后挂起它,把他的EIP指向须要执行的一段代码(把驱动层须要注入的这段代码叫ShellCodde),工具

执行完以后再回到线程原来的地方继续执行。测试

或者HOOK某些常常被调用的系统函数,好比NtCreateThread等,而后把ShellCode注入到当前进程去执行。this

方法不下七八种之多。spa

无非就是在驱动层里主动把ShellCode注入到某个进程执行,或者被动的当某个进程进入驱动以后,而后调用ShellCode。.net

虽然办法挺多,可是因为出现得比较早,并且又不是微软提倡的,也没获得微软的支持,因此兼容性不好。线程

每每大部分办法只对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处理吧。

就为了执行那么一小段代码,搞的这么复杂,因此没事仍是别作这么逆天的玩意。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3