驅動裏執行應用層代碼之KeUserModeCallBack,支持64位win7(包括WOW64)

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處理吧。

就爲了執行那麼一小段代碼,搞的這麼複雜,因此沒事仍是別作這麼逆天的玩意。

相關文章
相關標籤/搜索