在驱动层(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来实现)的更多相关文章

  1. java 里执行javascript代码

    import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; ScriptEngineManager sem = ...

  2. MongoDB:利用官方驱动改装为EF代码风格的MongoDB.Repository框架 三

    本次改动的主要内容是实现MongoDB.Repository在MongoDB中建立索引. 建立索引主要使用MongoDB的官方驱动中EnsureIndex方法. 在MongoDB.Repository ...

  3. SQL里执行CLR c#代码

    这里只说一个重点: 1.直接在sql里执行clr代码的时候,sql还是会报错 说没有启用 clr 执行以下代码才会起作用 EXEC sp_configure 'clr enabled', 1;  RE ...

  4. 在 Linux/windows下 命令行中使用和执行 PHP 代码[交互式php]

    [注释]在ubuntu下,升级php到7.1版本,虽然提示的是Interactive mode enabled, 但实际上可以直接书写命令,和interactive shell效果一样. 一:wind ...

  5. 静态库动态库的编译、链接, binutils工具集, 代码段\数据段\bss段解释

    #1. 如何使用静态库 制作静态库 (1)gcc *.c -c -I../include得到o文件 (2) ar rcs libMyTest.a *.o 将所有.o文件打包为静态库,r将文件插入静态库 ...

  6. 把纯C的动态库代码改造成C++版的

    近期想把一份纯C的跨Win/Linux的动态库工程代码改成支持C++编译器,这样用C++写起代码来比较顺手.要点是保证动态库的ABI一致性,既导出接口不能改变. 主要的改动有: 1.把.c后缀名换成. ...

  7. jsp页面:js方法里嵌套java代码(是操作数据库的),如果这个js 方法没被调用,当jsp页面被解析的时候,不管这个js方法有没有被调用这段java代码都会被执行?

    jsp页面:js方法里嵌套java代码(是操作数据库的),如果这个js 方法没被调用,当jsp页面被解析的时候,不管这个js方法有没有被调用这段java代码都会被执行? 因为在解析时最新解析的就是JA ...

  8. 2、CC2541芯片中级教程-OSAL操作系统(进一步了解-OLED && 普通按键和5方向按键-中断!!!)这个系统驱动层和应用层不一样~

    本文根据一周CC2541笔记汇总得来—— 适合概览和知识快速索引—— 全部链接: 中级教程-OSAL操作系统\OSAL操作系统-实验01 OSAL初探 [插入]SourceInsight-工程建立方法 ...

  9. Minifilter微过滤框架:框架介绍以及驱动层和应用层的通讯

    minifilter是sfilter后微软推出的过滤驱动框架.相比于sfilter,他更容易使用,需要程序员做的编码更简洁. 系统为minifilter专门制作了一个过滤管理器,这个管理器本身其实是一 ...

随机推荐

  1. tomcat绿色版及安装版修改内存大小的方法

    1.对于安装版,比较方便了,直接运行tomcat6w.exe,选择Java选项卡, 在这里,可以设置初始化内存,最大内存,线程的内存大小. 初始化内存:如果机器的内存足够大,可以直接将初始化内存设置为 ...

  2. 必胜宅急送Web app设计背后的思考

    O2O模式是餐饮业在移动消费趋势下主动拥抱互联网的方向,迎合餐饮消费者从以往经验判断为主转变为依靠移动设备.lbs.社交网络进行立体决策的过程.继App客户端之后,手机web app也逐渐成为O2O中 ...

  3. 【技术文档】《算法设计与分析导论》R.C.T.Lee等·第4章 分治策略

    分治策略有一种“大事化小,小事化了”的境界,它的思想是将原问题分解成两个子问题,两个子问题的性质和原问题相同,因此这两个子问题可以再用分治策略求解,最终将两个子问题的解合并成原问题的解.有时,我们会有 ...

  4. 使用Dreamwaver cc中的SVN功能,用于传输BAE和SAE中的文件

    前沿: 假期使用BAE和SAE开发应用,两个服务器都需要通过SVN提交代码,因为平时大多使用Dreamwaver,所以查了查资料,通过Subversion方便了开发. 因为网上的资料都不全,所以根据自 ...

  5. [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 ...

  6. javascript实现Map

    function Map() { this.elements = new Array(); // 获取MAP元素个数 this.size = function() { return this.elem ...

  7. composer之安装

    最近想要学习下yii框架,所以,就看了下官网,看到了貌似比较依赖composer这个东西,然后我就安装了,但是会有问题,安装不上等等问题,不论是windows还是linux命令行安装,都是因为一个问题 ...

  8. Canvas画椭圆的方法

    虽然标题是画椭圆,但是我们先来说说Canvas中的圆 相信大家对于Canvas画圆都不陌生   oGC.arc(400, 300, 100, 0, 2*Math.PI, false); 如上所示,直接 ...

  9. Linq101-Miscellaneous

    using System; using System.Collections.Generic; using System.Linq; namespace Linq101 { class Miscell ...

  10. Ajax的工作流程简述

    提到Ajax相信我们都不会陌生,不管你是前端开发还是后台数据处理的程序员,ajax的作用就像现在生活中的手机一样,无论是作用还是流程都差不多,这里我们要进行ajax操作后台数据并显示在页面上的话,首先 ...