Monday, 19 June 2017

Practical Reverse Engineering Exercise Solutions: KeInitializeApc Routine

To keep me motivated and document my progress, I will create a series of blog posts with answers to some of the exercises from the book "Practical Reverse Engineering" by Dang, Gazet and Bachaalany.

In the last post, we introduced the Windows Kernel Debugger (KD) and some of the functions. I have learned that rather than using KD directly, we can use WinDbg's interface which is more user-friendly.
When calling livekd, simply append the "-w" parameter and WinDbg will start up:


Let us now proceed with the task of decompiling the Windows Kernel routine KeInitializeApc.


The first odditiy that caught my attention was the  instruction mov edi, edi right at the beginning of the function. What is its purpose?
this instruction is a common practice in Windows and acts as a two-byte NOP, which is inserted to allow for hot-patching. In a nutshell, it is used for dynamically replacing the mov edi, edi instruction with a JUMP instruction.

Returning to the KeInitializeApc function, we should examine its signature first. Unfortunately, the function is not officially documented at MSDN. Nevertheless, it is documented in several forums (e.g., https://forum.sysinternals.com/howto-capture-kernel-stack-traces_topic19356.html) :

NTKERNELAPI VOID KeInitializeApc(
    PKAPC Apc,
    PKTHREAD Thread,
    KAPC_ENVIRONMENT Environment,
    PKKERNEL_ROUTINE KernelRoutine,
    PKRUNDOWN_ROUTINE RundownRoutine,
    PKNORMAL_ROUTINE NormalRoutine,
    KPROCESSOR_MODE ProcessorMode,
    PVOID NormalContext
    );

The PKAPC structure is defined as follows:


The KTHREAD structure is defined as follows: (the only referred property is at 0x134)
0: kd> dt nt!_kthread
   +0x000 Header           : _DISPATCHER_HEADER
   +0x010 CycleTime        : Uint8B
   +0x018 HighCycleTime    : Uint4B
   +0x020 QuantumTarget    : Uint8B
   +0x028 InitialStack     : Ptr32 Void
   +0x02c StackLimit       : Ptr32 Void
   +0x030 KernelStack      : Ptr32 Void
   +0x034 ThreadLock       : Uint4B
   +0x038 WaitRegister     : _KWAIT_STATUS_REGISTER
   +0x039 Running          : UChar
   +0x03a Alerted          : [2] UChar
   +0x03c KernelStackResident : Pos 0, 1 Bit
   +0x03c ReadyTransition  : Pos 1, 1 Bit
   +0x03c ProcessReadyQueue : Pos 2, 1 Bit
   +0x03c WaitNext         : Pos 3, 1 Bit
   +0x03c SystemAffinityActive : Pos 4, 1 Bit
   +0x03c Alertable        : Pos 5, 1 Bit
   +0x03c GdiFlushActive   : Pos 6, 1 Bit
   +0x03c UserStackWalkActive : Pos 7, 1 Bit
   +0x03c ApcInterruptRequest : Pos 8, 1 Bit
   +0x03c ForceDeferSchedule : Pos 9, 1 Bit
   +0x03c QuantumEndMigrate : Pos 10, 1 Bit
   +0x03c UmsDirectedSwitchEnable : Pos 11, 1 Bit
   +0x03c TimerActive      : Pos 12, 1 Bit
   +0x03c SystemThread     : Pos 13, 1 Bit
   +0x03c Reserved         : Pos 14, 18 Bits
   +0x03c MiscFlags        : Int4B
   +0x040 ApcState         : _KAPC_STATE
   +0x040 ApcStateFill     : [23] UChar
   +0x057 Priority         : Char
   +0x058 NextProcessor    : Uint4B
   +0x05c DeferredProcessor : Uint4B
   +0x060 ApcQueueLock     : Uint4B
   +0x064 ContextSwitches  : Uint4B
   +0x068 State            : UChar
   +0x069 NpxState         : Char
   +0x06a WaitIrql         : UChar
   +0x06b WaitMode         : Char
   +0x06c WaitStatus       : Int4B
   +0x070 WaitBlockList    : Ptr32 _KWAIT_BLOCK
   +0x074 WaitListEntry    : _LIST_ENTRY
   +0x074 SwapListEntry    : _SINGLE_LIST_ENTRY
   +0x07c Queue            : Ptr32 _KQUEUE
   +0x080 WaitTime         : Uint4B
   +0x084 KernelApcDisable : Int2B
   +0x086 SpecialApcDisable : Int2B
   +0x084 CombinedApcDisable : Uint4B
   +0x088 Teb              : Ptr32 Void
   +0x090 Timer            : _KTIMER
   +0x0b8 AutoAlignment    : Pos 0, 1 Bit
   +0x0b8 DisableBoost     : Pos 1, 1 Bit
   +0x0b8 EtwStackTraceApc1Inserted : Pos 2, 1 Bit
   +0x0b8 EtwStackTraceApc2Inserted : Pos 3, 1 Bit
   +0x0b8 CalloutActive    : Pos 4, 1 Bit
   +0x0b8 ApcQueueable     : Pos 5, 1 Bit
   +0x0b8 EnableStackSwap  : Pos 6, 1 Bit
   +0x0b8 GuiThread        : Pos 7, 1 Bit
   +0x0b8 UmsPerformingSyscall : Pos 8, 1 Bit
   +0x0b8 VdmSafe          : Pos 9, 1 Bit
   +0x0b8 UmsDispatched    : Pos 10, 1 Bit
   +0x0b8 ReservedFlags    : Pos 11, 21 Bits
   +0x0b8 ThreadFlags      : Int4B
   +0x0bc ServiceTable     : Ptr32 Void
   +0x0c0 WaitBlock        : [4] _KWAIT_BLOCK
   +0x120 QueueListEntry   : _LIST_ENTRY
   +0x128 TrapFrame        : Ptr32 _KTRAP_FRAME
   +0x12c FirstArgument    : Ptr32 Void
   +0x130 CallbackStack    : Ptr32 Void
   +0x130 CallbackDepth    : Uint4B
   +0x134 ApcStateIndex    : UChar
   +0x135 BasePriority     : Char
   +0x136 PriorityDecrement : Char
   +0x136 ForegroundBoost  : Pos 0, 4 Bits
   +0x136 UnusualBoost     : Pos 4, 4 Bits
   +0x137 Preempted        : UChar
   +0x138 AdjustReason     : UChar
   +0x139 AdjustIncrement  : Char
   +0x13a PreviousMode     : Char
   +0x13b Saturation       : Char
   +0x13c SystemCallNumber : Uint4B
   +0x140 FreezeCount      : Uint4B
   +0x144 UserAffinity     : _GROUP_AFFINITY
   +0x150 Process          : Ptr32 _KPROCESS
   +0x154 Affinity         : _GROUP_AFFINITY
   +0x160 IdealProcessor   : Uint4B
   +0x164 UserIdealProcessor : Uint4B
   +0x168 ApcStatePointer  : [2] Ptr32 _KAPC_STATE
   +0x170 SavedApcState    : _KAPC_STATE
   +0x170 SavedApcStateFill : [23] UChar
   +0x187 WaitReason       : UChar
   +0x188 SuspendCount     : Char
   +0x189 Spare1           : Char
   +0x18a OtherPlatformFill : UChar
   +0x18c Win32Thread      : Ptr32 Void
   +0x190 StackBase        : Ptr32 Void
   +0x194 SuspendApc       : _KAPC
   +0x194 SuspendApcFill0  : [1] UChar
   +0x195 ResourceIndex    : UChar
   +0x194 SuspendApcFill1  : [3] UChar
   +0x197 QuantumReset     : UChar
   +0x194 SuspendApcFill2  : [4] UChar
   +0x198 KernelTime       : Uint4B
   +0x194 SuspendApcFill3  : [36] UChar
   +0x1b8 WaitPrcb         : Ptr32 _KPRCB
   +0x194 SuspendApcFill4  : [40] UChar
   +0x1bc LegoData         : Ptr32 Void
   +0x194 SuspendApcFill5  : [47] UChar
   +0x1c3 LargeStack       : UChar
   +0x1c4 UserTime         : Uint4B
   +0x1c8 SuspendSemaphore : _KSEMAPHORE
   +0x1c8 SuspendSemaphorefill : [20] UChar
   +0x1dc SListFaultCount  : Uint4B
   +0x1e0 ThreadListEntry  : _LIST_ENTRY
   +0x1e8 MutantListHead   : _LIST_ENTRY
   +0x1f0 SListFaultAddress : Ptr32 Void
   +0x1f4 ThreadCounters   : Ptr32 _KTHREAD_COUNTERS
   +0x1f8 XStateSave       : Ptr32 _XSTATE_SAVE


Lastly, the _KAPC_ENVIRONMENT data type is an enum type:

typedef enum _KAPC_ENVIRONMENT
{
    OriginalApcEnvironment,
    AttachedApcEnvironment,
    CurrentApcEnvironment,
    InsertApcEnvironment
} KAPC_ENVIRONMENT, *PKAPC_ENVIRONMENT;

The initial attempt to translate the routine from above plainly into C/C++ code results in the following pseudo code:

eax = apc (ebp+0x8)
edx = environment (ebp+0x10)
ecx = thread (ebp+0xC)
eax->type = 0x12
eax->size = 0x30

if (edx != 2) goto 0x20

edx = ecx ->ApcStateIndex (offset 0x134)

0x20: 
eax->Thread = ecx
ecx = KernelRoutine (ebp+0x14)
eax->KernelRoutine = KernelRoutine
ecx = RundownRoutine (ebp+0x18)
eax->RundownRoutine = RundownRoutine
eax->ApcStateIndex = edx
ecx = NormalRoutine (ebp+0x1C)
eax->NormalRoutine = NormalRoutine
edx = 0
if (NormalRoutine == 0) goto 0x4C

ecx = ProcessorMode (ebp+0x20)
eax->ApcMode = ecx
ecx = NormalContext (ebp+0x24)
eax->NormalContext = ecx
goto 0x52

0x4C:
eax->ApcMode = 0
eax->NormalContext = 0

0x52:
eax->InsertedMode = 0
return

Obviously, this can be improved. The first if-statement compares the environment value to the integer value 2. According to the _KAPC_ENVIRONMENT datatype, the corresponding value for the integer 2 is CurrentApcEnvironment (starting from 0).

NTKERNELAPI VOID KeInitializeApc(
    PKAPC Apc,
    PKTHREAD Thread,
    KAPC_ENVIRONMENT Environment,
    PKKERNEL_ROUTINE KernelRoutine,
    PKRUNDOWN_ROUTINE RundownRoutine,
    PKNORMAL_ROUTINE NormalRoutine,
    KPROCESSOR_MODE ProcessorMode,
    PVOID NormalContext
    ) {
Apc->type = 0x12;
Apc->size = 0x30;
if (Environment == CurrentApcEnvironment){
Environment = Thread->ApcStateIndex;
}

init: 
Apc->Thread = Thread;
Apc->KernelRoutine = KernelRoutine;
Apc->RundownRoutine = RundownRoutine;
Apc->ApcStateIndex = Environment;
Apc->NormalRoutine = NormalRoutine;

if (NormalRoutine == 0) {
Apc->ApcMode = 0;
Apc->NormalContext = 0;
}
else {
Apc->ApcMode = ProcessorMode;
Apc->NormalContext = NormalContext;
}
Apc->InsertedMode = 0;
return;
}

If you have found any mistakes in the decompilation, I would really appreciate your feedback.
For now we have more or less stupidly translated the routine to C/C++ without understanding any of the internal mechanics, let alone what Apc actually stands for. As reverse engineering at Windows requires a deep knowledge of the Windows internals, let us approach MSDN (https://msdn.microsoft.com/de-de/library/windows/desktop/ms681951(v=vs.85).aspx):

It states:
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.

Thus, our function generates a kernel-mode asynchronous procedure call (APC) based on the data submitted in the parameters. 

No comments:

Post a Comment