This is part 3 of the intro to kernel debugging series. Other posts:
In this post, we will explore the following:
- Probe memory of a user mode process
- Alter user mode process memory
Reminders about how this tutorial is authored:
- The author is a user mode developer, and tailors the conversation to that audience
- Always make sure you have symbols loaded!
- We are using a kd connection to a VM, as explained in a previous tutorial
Setup Test Apparatus
Let’s create a simple user mode app to test with. For simplicity, we want a test app that runs for a long time, making it easier to break into the app and inspect its state. Here is the app we will be using:
#include <windows.h>#include <iostream>void main(void){ std::wcout << L"Hello, world"<< std::endl; for (int i = 0; ; i++) { std::wcout << L"Loop iteration: "<< i << std::endl; Sleep(1000); }}
Having the test app wake up from time to time and inspect state will help illustrate the ability to modify state.
In a previous tutorial, we used a kd connection to a local VM. To set up the test apparatus, compile the above code and run the app on the VM OS.
Inspecting User Mode Process
Start by breaking into the debugger with control+break (windbg) or control+c (kd). As always, make sure you have symbols loaded:
Microsoft (R) Windows Debugger Version 10.0.10586.567 AMD64Copyright (c) Microsoft Corporation. All rights reserved.Opened \.pipekdWaiting to reconnect...Connected to Windows 10 10240 x64 target at (Wed Jun 29 18:03:54.125 2016 (UTC - 7:00)), ptr64 TRUEKernel Debugger connection established.Symbol search path is: srv*Executable search path is: Windows 10 Kernel Version 10240 MP (4 procs) Free x64Product: WinNt, suite: TerminalServer SingleUserTSBuilt by: 10240.16841.amd64fre.th1_st1.160408-1853Machine Name:Kernel base = 0xfffff800`8f685000 PsLoadedModuleList = 0xfffff800`8f9aa070Debug session time: Wed Jun 29 18:03:52.560 2016 (UTC - 7:00)System Uptime: 0 days 4:34:42.939Break instruction exception - code 80000003 (first chance)******************************************************************************** ** You are seeing this message because you pressed either ** CTRL+C (if you run console kernel debugger) or, ** CTRL+BREAK (if you run GUI kernel debugger), ** on your debugger machine's keyboard. ** ** THIS IS NOT A BUG OR A SYSTEM CRASH ** ** If you did not intend to break into the debugger, press the "g" key, then ** press the "Enter" key now. This message might immediately reappear. If it ** does, press "g" and "Enter" again. ** ********************************************************************************nt!DbgBreakPointWithStatus:fffff800`8f7d9bb0 cc int 3 0: kd> .sympath cache*d:sym;srv*Symbol search path is: cache*d:sym;srv*Expanded Symbol search path is: cache*d:sym;SRV*https://msdl.microsoft.com/download/symbols************* Symbol Path validation summary **************Response Time (ms) LocationDeferred cache*d:symDeferred srv* 0: kd> .reload /f *.**** WARNING: Unable to verify timestamp for msrpc.sys*** ERROR: Module load completed but symbols could not be loaded for msrpc.sys*** ERROR: Symbol file could not be found. Defaulted to export symbols for clipsp.sys - Press ctrl-c (cdb, kd, ntsd) or ctrl-break (windbg) to abort symbol loads that take too long.Run !sym noisy before .reload to track down problems loading symbols.*** WARNING: Unable to verify timestamp for spaceport.sys*** ERROR: Module load completed but symbols could not be loaded for spaceport.sys*** WARNING: Unable to verify timestamp for volmgrx.sys*** ERROR: Module load completed but symbols could not be loaded for volmgrx.sys*** WARNING: Unable to verify timestamp for Fs_Rec.sys*** ERROR: Module load completed but symbols could not be loaded for Fs_Rec.sys*** WARNING: Unable to verify timestamp for Null.SYS*** ERROR: Module load completed but symbols could not be loaded for Null.SYS*** ERROR: Module load completed but symbols could not be loaded for peauth.sys
I gave a name to my test app, and I will be searching for that name in the process list: meason_test.exe. The following command does the search:
0: kd> !process 0 0 meason_test.exe
PROCESS ffffe0016723c840
SessionId: 1 Cid: 1078 Peb: 7ff7e1ed5000 ParentCid: 0eec
DirBase: 4a4ea000 ObjectTable: ffffc00063ac0c80 HandleCount: <Data Not Accessible>
Image: meason_test.exe
Let’s take a peek at what the process is doing. We can do that by looking at the call stacks of all threads in the process, which can be done by passing the 0x17 value as the second argument to !process. However, before we can do that, we must make sure the debugger can find the user mode symbols:
0: kd> .sympath+ D:_inctestamd64Symbol search path is: cache*d:sym;srv*;D:_inctestamd64Expanded Symbol search path is: cache*d:sym;SRV*https://msdl.microsoft.com/download/symbols;d:_inctestamd64
You may wish to optimize the symbol search by putting the private symbol path before the public symbol path, but we won’t do that here.
On to the process inspection with the 0x17 option. Use the nt!_EPROCESS address that was output in the above !process search (it has the word “PROCESS” next to it in all caps).
0: kd> !process ffffe0016723c840 17PROCESS ffffe0016723c840 SessionId: 1 Cid: 1078 Peb: 7ff7e1ed5000 ParentCid: 0eec DirBase: 4a4ea000 ObjectTable: ffffc00063ac0c80 HandleCount: <Data Not Accessible> Image: meason_test.exe VadRoot ffffe001667c91e0 Vads 20 Clone 0 Private 88. Modified 0. Locked 0. DeviceMap ffffc0006a2b8870 Token ffffc00063c58060 ElapsedTime 00:00:31.376 UserTime 00:00:00.000 KernelTime 00:00:00.000 QuotaPoolUsage[PagedPool] 18696 QuotaPoolUsage[NonPagedPool] 2712 Working Set Sizes (now,min,max) (512, 50, 345) (2048KB, 200KB, 1380KB) PeakWorkingSetSize 486 VirtualSize 2097160 Mb PeakVirtualSize 2097161 Mb PageFaultCount 512 MemoryPriority BACKGROUND BasePriority 8 CommitCharge 84 Setting context for this process... ffffffffffffffff NotificationEvent Not impersonating DeviceMap ffffc0006a2b8870 Owning Process ffffe0016723c840 Image: meason_test.exe Attached Process N/A Image: N/A Wait Start TickCount 1078268 Ticks: 4 (0:00:00:00.062) Context Switch Count 1265 IdealProcessor: 1 UserTime 00:00:00.000 KernelTime 00:00:00.015 Win32 Start Address meason_test!mainCRTStartup (0x00007ff7e281b9f0) Stack Init ffffd0010e766c90 Current ffffd0010e766790 Base ffffd0010e767000 Limit ffffd0010e761000 Call 0 Priority 8 BasePriority 8 UnusualBoost 0 ForegroundBoost 0 IoPriority 2 PagePriority 5 Child-SP RetAddr : Args to Child : Call Site ffffd001`0e7667d0 fffff800`8f6d92a0 : ffffe001`00000000 00000000`00000001 00000000`00000000 ffffe001`63b4f440 : nt!KiSwapContext+0x76 ffffd001`0e766910 fffff800`8f6d8cb8 : ffffe001`63b4f340 fffff800`8f9eb780 000000b6`b5522ee7 ffffe001`670bbf20 : nt!KiSwapThread+0x160 ffffd001`0e7669c0 fffff800`8f709d89 : 00000027`3ac0b8cd 00000000`00000001 00000000`000000b0 000000fe`a11bf910 : nt!KiCommitThreadWait+0x148 ffffd001`0e766a50 fffff800`8fb2ecac : ffffe001`63b4f340 00000000`00000000 00000000`00000000 000000fe`a11bf900 : nt!KeDelayExecutionThread+0x229 ffffd001`0e766ad0 fffff800`8f7deb63 : ffffe001`63b4f340 00007ff7`e1ed5000 ffffffff`ff676980 ffffe001`6427c350 : nt!NtDelayExecution+0x5c ffffd001`0e766b00 00007fff`c5803b6a : 00007fff`c2be3757 000000fe`a11bfbd8 000000fe`a11bfb01 ffffffff`00000000 : nt!KiSystemServiceCopyEnd+0x13 (TrapFrame @ ffffd001`0e766b00) 000000fe`a11bfb18 00007fff`c2be3757 : 000000fe`a11bfbd8 000000fe`a11bfb01 ffffffff`00000000 00007ff7`e2821440 : ntdll!NtDelayExecution+0xa 000000fe`a11bfb20 00007ff7`e2818323 : 00000000`00000001 00007ff7`00000000 00000000`00000000 00000000`00000000 : KERNELBASE!SleepEx+0xa7 000000fe`a11bfbc0 00007ff7`e281b96d : 00000000`00000001 00000000`00000000 00000000`00000000 00007ff7`e281d3d0 : meason_test!main+0x73 [d:testmeason_testmain.cpp @ 13] 000000fe`a11bfc00 00007fff`c3c22d92 : 00007ff7`e281b9f0 00007ff7`e1ed5000 00007ff7`e1ed5000 00000000`00000000 : meason_test!__mainCRTStartup+0x14d [d:(omitted) @ 697] 000000fe`a11bfc40 00007fff`c5779f64 : 00007fff`c3c22d70 00000000`00000000 00000000`00000000 00000000`00000000 : KERNEL32!BaseThreadInitThunk+0x22 000000fe`a11bfc70 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x34
Probe User Mode Memory
At this point, we could attempt to query user mode memory with the ‘u’ command (unassemble). However, it likely won’t work as expected. Let’s inspect a return address from the call stack above (chosen carefully to ensure we are looking at our own app):
0: kd> u 00007ff7`e281832300007ff7`e2818323 ?? ??? ^ Memory access error in 'u 00007ff7`e2818323'
As we learned in the last tutorial, when you first break in with the kernel debugger, your debugger context may not necessarily be set to where you want it to be. That’s why we are unable to read this address: the debugger is using some other process context, and it is one in which this address does not translate to a valid page.
Let’s set the right debugger context for our test app. Use the nt!_EPROCESS address that was output in the !process command above.
0: kd> .process /p /r ffffe0016723c840Implicit process is now ffffe001`6723c840.cache forcedecodeuser doneLoading User Symbols.....************* Symbol Loading Error Summary **************Module name Errormsrpc The system cannot find the file specifiedclipsp The system cannot find the file specifiedspaceport The system cannot find the file specifiedvolmgrx The system cannot find the file specifiedFs_Rec The system cannot find the file specifiedNull The system cannot find the file specifiedpeauth The system cannot find the file specified You can troubleshoot most symbol related issues by turning on symbol loading diagnostics (!sym noisy) and repeating the command that caused symbols to be loaded.You should also verify that your symbol search path (.sympath) is correct.
You should now be able to unassemble the user mode call stack address:
0: kd> ub 00007ff7`e2818323
meason_test!main+0x4d [d:testmeason_testmain.cpp @ 11]:
00007ff7`e28182fd 8b542420 mov edx,dword ptr [rsp+20h]
00007ff7`e2818301 488bc8 mov rcx,rax
00007ff7`e2818304 e887a6ffff call meason_test!std::basic_ostream<wchar_t,std::char_traits<wchar_t> >::operator<< (00007ff7`e2812990)
00007ff7`e2818309 488d15a0f1ffff lea rdx,[meason_test!std::endl (00007ff7`e28174b0)]
00007ff7`e2818310 488bc8 mov rcx,rax
00007ff7`e2818313 e8e8a8ffff call meason_test!std::basic_ostream<wchar_t,std::char_traits<wchar_t> >::operator<< (00007ff7`e2812c00)
00007ff7`e2818318 b9e8030000 mov ecx,3E8h
00007ff7`e281831d ff159d4d0000 call qword ptr [meason_test!_imp_Sleep (00007ff7`e281d0c0)]
0: kd> u
meason_test!main+0x73 [d:testmeason_testmain.cpp @ 13]:
00007ff7`e2818323 ebbb jmp meason_test!main+0x30 (00007ff7`e28182e0)
00007ff7`e2818325 4883c438 add rsp,38h
00007ff7`e2818329 c3 ret
00007ff7`e281832a cc int 3
00007ff7`e281832b cc int 3
00007ff7`e281832c cc int 3
00007ff7`e281832d cc int 3
00007ff7`e281832e cc int 3
Observe that we have user mode code showing up in the kernel debugger! The use of the ‘ub’ unassembled command was chosen in order to show the assembly code leading up to our Sleep() call.
Alter User Memory
Let’s start messing around with the memory in our app! Recall that our test app from above had a running counter. Let’s edit its value. While the app is running (and the kernel debugger is in run mode, not break mode), you will see the loop iteration keeps increasing:
c:testamd64>meason_test.exe Hello, world Loop iteration: 0 Loop iteration: 1 Loop iteration: 2 Loop iteration: 3 Loop iteration: 4 Loop iteration: 5
Break into the debugger, find our test process, and set the debugger context to that process:
0: kd> !process 0 0 meason_test.exePROCESS ffffe00138301840 SessionId: 1 Cid: 0450 Peb: 7ff7a568f000 ParentCid: 0cb0 DirBase: 21cb6000 ObjectTable: ffffc001ae412840 HandleCount: <Data Not Accessible> Image: meason_test.exe 0: kd> .process /p /r ffffe00138301840Implicit process is now ffffe001`38301840.cache forcedecodeuser doneLoading User Symbols.....
0: kd> !process ffffe00138301840 fPROCESS ffffe00138301840 SessionId: 1 Cid: 0450 Peb: 7ff7a568f000 ParentCid: 0cb0 DirBase: 21cb6000 ObjectTable: ffffc001ae412840 HandleCount: <Data Not Accessible> Image: meason_test.exe VadRoot ffffe00138d16010 Vads 20 Clone 0 Private 89. Modified 0. Locked 0. DeviceMap ffffc001a6c4c710 Token ffffc001a7633060 ElapsedTime 00:01:28.220 UserTime 00:00:00.000 KernelTime 00:00:00.000 QuotaPoolUsage[PagedPool] 18696 QuotaPoolUsage[NonPagedPool] 2712 Working Set Sizes (now,min,max) (518, 50, 345) (2072KB, 200KB, 1380KB) PeakWorkingSetSize 491 VirtualSize 2097160 Mb PeakVirtualSize 2097162 Mb PageFaultCount 519 MemoryPriority BACKGROUND BasePriority 8 CommitCharge 86 THREAD ffffe00138d9a640 Cid 0450.0e2c Teb: 00007ff7a568d000 Win32Thread: 0000000000000000 WAIT: (DelayExecution) UserMode Non-Alertable ffffffffffffffff NotificationEvent Not impersonating DeviceMap ffffc001a6c4c710 Owning Process ffffe00138301840 Image: meason_test.exe Attached Process N/A Image: N/A Wait Start TickCount 11054 Ticks: 64 (0:00:00:01.000) Context Switch Count 1114 IdealProcessor: 1 UserTime 00:00:00.000 KernelTime 00:00:00.031 Win32 Start Address meason_test!mainCRTStartup (0x00007ff7a5b9b9f0) Stack Init ffffd0015490dc90 Current ffffd0015490d790 Base ffffd0015490e000 Limit ffffd00154908000 Call 0 Priority 8 BasePriority 8 UnusualBoost 0 ForegroundBoost 0 IoPriority 2 PagePriority Child-SP RetAddr Call Site ffffd001`5490d7d0 fffff801`8f4592a0 nt!KiSwapContext+0x76 ffffd001`5490d910 fffff801`8f458cb8 nt!KiSwapThread+0x160 ffffd001`5490d9c0 fffff801`8f489d89 nt!KiCommitThreadWait+0x148 ffffd001`5490da50 fffff801`8f8aecac nt!KeDelayExecutionThread+0x229 ffffd001`5490dad0 fffff801`8f55eb63 nt!NtDelayExecution+0x5c ffffd001`5490db00 00007ffa`5a313b6a nt!KiSystemServiceCopyEnd+0x13 (TrapFrame @ ffffd001`5490db00) 000000c5`afc5f7d8 00007ffa`57743757 ntdll!NtDelayExecution+0xa 000000c5`afc5f7e0 00007ff7`a5b98323 KERNELBASE!SleepEx+0xa7 000000c5`afc5f880 00007ff7`a5b9b96d meason_test!main+0x73 [d:testmeason_testmain.cpp @ 13] 000000c5`afc5f8c0 00007ffa`57f42d92 meason_test!__mainCRTStartup+0x14d [(omitted) @ 697] 000000c5`afc5f900 00007ffa`5a289f64 KERNEL32!BaseThreadInitThunk+0x22 000000c5`afc5f930 00000000`00000000 ntdll!RtlUserThreadStart+0x34
0: kd> .thread /p /r ffffe00138d9a640 Implicit thread is now ffffe001`38d9a640Implicit process is now ffffe001`38301840.cache forcedecodeuser doneLoading User Symbols.....0: kd> k *** Stack trace for last set context - .thread/.cxr resets it # Child-SP RetAddr Call Site00 ffffd001`5490d7d0 fffff801`8f4592a0 nt!KiSwapContext+0x7601 ffffd001`5490d910 fffff801`8f458cb8 nt!KiSwapThread+0x16002 ffffd001`5490d9c0 fffff801`8f489d89 nt!KiCommitThreadWait+0x14803 ffffd001`5490da50 fffff801`8f8aecac nt!KeDelayExecutionThread+0x22904 ffffd001`5490dad0 fffff801`8f55eb63 nt!NtDelayExecution+0x5c05 ffffd001`5490db00 00007ffa`5a313b6a nt!KiSystemServiceCopyEnd+0x1306 000000c5`afc5f7d8 00007ffa`57743757 ntdll!NtDelayExecution+0xa07 000000c5`afc5f7e0 00007ff7`a5b98323 KERNELBASE!SleepEx+0xa708 000000c5`afc5f880 00007ff7`a5b9b96d meason_test!main+0x73 [d:testmeason_testmain.cpp @ 13]09 000000c5`afc5f8c0 00007ffa`57f42d92 meason_test!__mainCRTStartup+0x14d [(omitted) @ 697]0a 000000c5`afc5f900 00007ffa`5a289f64 KERNEL32!BaseThreadInitThunk+0x220b 000000c5`afc5f930 00000000`00000000 ntdll!RtlUserThreadStart+0x34
0: kd> .frame 808 000000c5`afc5f880 00007ff7`a5b9b96d meason_test!main+0x73 [d:testmeason_testmain.cpp @ 13]0: kd> dv i = 0n27
0: kd> u meason_test!mainmeason_test!main [d:testmeason_testmain.cpp @ 6]:00007ff7`a5b982b0 4883ec38 sub rsp,38h00007ff7`a5b982b4 488d1515580000 lea rdx,[meason_test!`string' (00007ff7`a5b9dad0)]00007ff7`a5b982bb 488d0d7e8d0000 lea rcx,[meason_test!std::wcout (00007ff7`a5ba1040)]00007ff7`a5b982c2 e8d98effff call meason_test!std::operator<<<wchar_t,std::char_traits<wchar_t> > (00007ff7`a5b911a0)00007ff7`a5b982c7 488d15e2f1ffff lea rdx,[meason_test!std::endl (00007ff7`a5b974b0)]00007ff7`a5b982ce 488bc8 mov rcx,rax00007ff7`a5b982d1 e82aa9ffff call meason_test!std::basic_ostream<wchar_t,std::char_traits<wchar_t> >::operator<< (00007ff7`a5b92c00)00007ff7`a5b982d6 c744242000000000 mov dword ptr [rsp+20h],00: kd> umeason_test!main+0x2e [d:testmeason_testmain.cpp @ 9]:00007ff7`a5b982de eb0a jmp meason_test!main+0x3a (00007ff7`a5b982ea)00007ff7`a5b982e0 8b442420 mov eax,dword ptr [rsp+20h]00007ff7`a5b982e4 ffc0 inc eax00007ff7`a5b982e6 89442420 mov dword ptr [rsp+20h],eax00007ff7`a5b982ea 488d15ff570000 lea rdx,[meason_test!`string' (00007ff7`a5b9daf0)]00007ff7`a5b982f1 488d0d488d0000 lea rcx,[meason_test!std::wcout (00007ff7`a5ba1040)]00007ff7`a5b982f8 e8a38effff call meason_test!std::operator<<<wchar_t,std::char_traits<wchar_t> > (00007ff7`a5b911a0)00007ff7`a5b982fd 8b542420 mov edx,dword ptr [rsp+20h]
0: kd> dd 000000c5`afc5f880+0x20000000c5`afc5f8a0 0000001b 00000000 00000000 00000000000000c5`afc5f8b0 00000000 00000000 a5b9b96d 00007ff7000000c5`afc5f8c0 00000001 00000000 00000000 00000000000000c5`afc5f8d0 00000000 00000000 a5b9d3d0 00007ff7000000c5`afc5f8e0 00000000 00000000 a5b9d3d0 00007ff7000000c5`afc5f8f0 00000000 00000000 57f42d92 00007ffa000000c5`afc5f900 a5b9b9f0 00007ff7 a568f000 00007ff7000000c5`afc5f910 a568f000 00007ff7 00000000 000000000: kd> ? 0000001bEvaluate expression: 27 = 00000000`0000001b
0: kd> ed 000000c5`afc5f880+0x20 0x123456780: kd> dd 000000c5`afc5f880+0x20000000c5`afc5f8a0 12345678 00000000 00000000 00000000000000c5`afc5f8b0 00000000 00000000 a5b9b96d 00007ff7000000c5`afc5f8c0 00000001 00000000 00000000 00000000000000c5`afc5f8d0 00000000 00000000 a5b9d3d0 00007ff7000000c5`afc5f8e0 00000000 00000000 a5b9d3d0 00007ff7000000c5`afc5f8f0 00000000 00000000 57f42d92 00007ffa000000c5`afc5f900 a5b9b9f0 00007ff7 a568f000 00007ff7000000c5`afc5f910 a568f000 00007ff7 00000000 00000000
Loop iteration: 25Loop iteration: 26Loop iteration: 27Loop iteration: 305419897Loop iteration: 305419898Loop iteration: 305419899
Summary
In this tutorial, we probed user memory with a sample app. We then altered the memory and saw the result. In all cases, the debugger context had to be set appropriately, else nothing would happen.