Wednesday, April 22, 2015

Debugging programs with multiple processes with windbg’s kernel mode debugger

It’s common to reverse malware (or any type of software) that creates multiple processes or loads drivers, and it is useful to be able to debug the new created processes or loaded drivers from entry point.

To break at the entry point of the processes you can modify CreateProcess parameters to create child processes suspended (to attach debugger later), or introduce 0xCC at entrypoint on disk of the PE file that is going to be launched, or use plugins for debuggers to attach to child processes… There are lot of methods. From kernel mode you can set conditions that break into the debugger: sxe ld ntdll.dll, sxe cpr, etc… For breaking at a driver entry point you can use: bu <drivername>!DriverEntry (though, i don’t know why, sometimes it doesn’t work for me).

I will talk here about a couple of ways to do this from windbg kernel mode debugger, without needing to restart computer to enable exceptions that break into debugger, or modifying PEs in disk,…

To break at entry point of the main module of a new process, we start setting a breakpoint at CreateProcessA, CreateProcessW, CreateProcessAsUserA, CreateProcessAsUserW, … of the process that will create the child process. If you don’t know what function is called to create the child process, set the bp at ZwCreateSection (it must be called always when a process is created). Step over the call (let the new process to be loaded). Now the new process’s image is loaded and you can set a bp for a target process with bp /p <process> <addr>:

When the process is loaded, search it with !process 0 0 to get the address of the EPROCESS.
PROCESS 8223d020  SessionId: 0  Cid: 0dd8    Peb: 7ffd9000  ParentCid: 0120
    DirBase: 093c0220  ObjectTable: e1224c08  HandleCount:   0.
    Image: calc.exe
With the EPROCESS address, enum process info:

kd> !process 8223d020

PROCESS 8223d020  SessionId: 0  Cid: 0dd8    Peb: 7ffd9000  ParentCid: 0120
    DirBase: 093c0220  ObjectTable: e1224c08  HandleCount:   0.
    Image: calc.exe
    VadRoot 82235f60 Vads 11 Clone 0 Private 8. Modified 0. Locked 0.
    DeviceMap e1b2b1b8
    Token                             e1274998
    ElapsedTime                       00:00:00.000
    UserTime                          00:00:00.000
    KernelTime                        00:00:00.000
    QuotaPoolUsage[PagedPool]         7860
    QuotaPoolUsage[NonPagedPool]      440
    Working Set Sizes (now,min,max)  (19, 50, 345) (76KB, 200KB, 1380KB)
    PeakWorkingSetSize                19
    VirtualSize                       2 Mb
    PeakVirtualSize                   2 Mb
    PageFaultCount                    12
    MemoryPriority                    BACKGROUND
    BasePriority                      8
    CommitCharge                      19

        THREAD 824304c0  Cid 0dd8.0dec  Teb: 7ffdf000 Win32Thread: 00000000 READY on processor 0
        Not impersonating
        DeviceMap                 e1b2b1b8
        Owning Process            0       Image:         <Unknown>
        Attached Process          8223d020       Image:         calc.exe
        Wait Start TickCount      70583          Ticks: 0
        Context Switch Count      0              IdealProcessor: 0             
        UserTime                  00:00:00.000
        KernelTime                00:00:00.000
        Win32 Start Address Explorer!CClockCtl::_GetMaxTimeSize (0x0101e23a)
        Start Address kernel32!BaseProcessStartThunk (0x7c810735)
        Stack Init f6b04000 Current f6b03d48 Base f6b04000 Limit f6b01000 Call 0
        Priority 8 BasePriority 8 PriorityDecrement 0 DecrementCount 0
        ChildEBP RetAddr  
        f6b03e28 7c920222 nt!KiThreadStartup
        f6b03eb0 7c924d12 ntdll!RtlpAllocateFromHeapLookaside+0x42 (FPO: [Non-Fpo])
        f6b03eb0 00000000 ntdll!RtlConvertSidToUnicodeString+0x1b5 (FPO: [Non-Fpo])
We can see the thread and use it to change the context to this thread:

kd> .thread 824304c0
Implicit thread is now 824304c0
kd> r
Last set context:
eax=00000000 ebx=00000000 ecx=00000000 edx=00000000 esi=00000000 edi=00000000
eip=80541f4c esp=f6b03d54 ebp=805c623e iopl=0         nv up di pl nz na po nc
cs=001b  ss=0023  ds=0000  es=0000  fs=0000  gs=0000             efl=00000000
001b:80541f4c 33db            xor     ebx,ebx
Now we see the ETHREAD struct of the main thread of the new process at address 0x824304c0, and we can see the fields of this structure. We are interesed on Win32StartAddress field, the entrypoint of the thread in the recently created process:

kd> .process /i 8223d020
You need to continue execution (press 'g' <enter>) for the context
to be switched. When the debugger breaks in again, you will be in
the new process context.
kd> dt _ETHREAD 824304c0
   +0x000 Tcb              : _KTHREAD
   +0x1c0 CreateTime       : _LARGE_INTEGER 0x0e82fb8c`bed0e210
   +0x1c0 NestedFaultCount : 0y00
   +0x1c0 ApcNeeded        : 0y0
   +0x1c8 ExitTime         : _LARGE_INTEGER 0x82430688`82430688
   +0x1c8 LpcReplyChain    : _LIST_ENTRY [ 0x82430688 - 0x82430688 ]
   +0x1c8 KeyedWaitChain   : _LIST_ENTRY [ 0x82430688 - 0x82430688 ]
   +0x1d0 ExitStatus       : 0n0
   +0x1d0 OfsChain         : (null) 
   +0x1d4 PostBlockList    : _LIST_ENTRY [ 0x82430694 - 0x82430694 ]
   +0x1dc TerminationPort  : (null) 
   +0x1dc ReaperLink       : (null) 
   +0x1dc KeyedWaitValue   : (null) 
   +0x1e0 ActiveTimerListLock : 0
   +0x1e4 ActiveTimerListHead : _LIST_ENTRY [ 0x824306a4 - 0x824306a4 ]
   +0x1ec Cid              : _CLIENT_ID
   +0x1f4 LpcReplySemaphore : _KSEMAPHORE
   +0x1f4 KeyedWaitSemaphore : _KSEMAPHORE
   +0x208 LpcReplyMessage  : (null) 
   +0x208 LpcWaitingOnPort : (null) 
   +0x20c ImpersonationInfo : (null) 
   +0x210 IrpList          : _LIST_ENTRY [ 0x824306d0 - 0x824306d0 ]
   +0x218 TopLevelIrp      : 0
   +0x21c DeviceToVerify   : (null) 
   +0x220 ThreadsProcess   : 0x8223d020 _EPROCESS
   +0x224 StartAddress     : 0x7c810735 Void
   +0x228 Win32StartAddress : 0x0101e23a Void
   +0x228 LpcReceivedMessageId : 0x101e23a
   +0x22c ThreadListEntry  : _LIST_ENTRY [ 0x8223d1b0 - 0x8223d1b0 ]
   +0x234 RundownProtect   : _EX_RUNDOWN_REF
   +0x238 ThreadLock       : _EX_PUSH_LOCK
   +0x23c LpcReplyMessageId : 0
   +0x240 ReadClusterSize  : 7
   +0x244 GrantedAccess    : 0x1f03ff
   +0x248 CrossThreadFlags : 0
   +0x248 Terminated       : 0y0
   +0x248 DeadThread       : 0y0
   +0x248 HideFromDebugger : 0y0
   +0x248 ActiveImpersonationInfo : 0y0
   +0x248 SystemThread     : 0y0
   +0x248 HardErrorsAreDisabled : 0y0
   +0x248 BreakOnTermination : 0y0
   +0x248 SkipCreationMsg  : 0y0
   +0x248 SkipTerminationMsg : 0y0
   +0x24c SameThreadPassiveFlags : 0
   +0x24c ActiveExWorker   : 0y0
   +0x24c ExWorkerCanWaitUser : 0y0
   +0x24c MemoryMaker      : 0y0
   +0x250 SameThreadApcFlags : 0
   +0x250 LpcReceivedMsgIdValid : 0y0
   +0x250 LpcExitThreadCalled : 0y0
   +0x250 AddressSpaceOwner : 0y0
   +0x254 ForwardClusterOnly : 0 ''
   +0x255 DisablePageFaultClustering : 0 ''
The field Win32StartAddress of ETHREAD contains the entrypoint, so we set a breakpoint in that process at that address with:

bp /p <process> <entrypoint>


ba e1 <entrypoint>

In this way we can debug from entrypoint of the process.

Usually, it’s interesting to suspend the new processes until you see what other processes are created, and want to debug them, etc… Windbg user-mode debugger let you to suspend threads easily (~Thread n) but you can’t do this from kernel-mode debugger. You can read a couple of articles about threads suspension and resumption here:

Windows Thread Suspension Internals Part 1

Windows Thread Suspension Internals Part 2

Windows Internals – Thread resumption and synchronization objects

I asked the author of these articles about suspending threads from kernel mode session and he answered me that “windbg raises the IRQL to its highest value to stop all threads. I don’t think u can do it for one thread only, many stuff done”. I would like to investigate it in deep in the future.

I tried to force the thread to call SuspendThread to suspend itself:

kd> r @esp = @esp - 8
kd> ed @esp @eip
kd> ed @esp+4 fffffffe
kd> r @eip = kernel32!SuspendThread
But it worked sometimes, other times the thread ended up in an unestable state.

About breaking at DriverEntry of a driver, it is possible to set a breakpoint at symbol with bu. But i noticed it doesn’t work sometimes, i don’t know why. You can set bp at MmLoadSystemImage function too. This function will load the driver, so when the bp is hit, you step out of the function, and, in that moment, the driver is mapped in memory. Now you can load symbols and set breakpoint at driver entry with bp <driver>!DriverEntry,…

No comments:

Post a Comment