Debugging programs with multiple processes with windbg’s kernel mode debugger
轉載自:http://www.vallejo.cc/2015/04/debugging-programs-with-multiple.html
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 nt!KiThreadStartup: 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 ntdll!_ETHREAD +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>
or
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,…