Partilhar via


Intro to kernel debugging 3

Topic: Probing, Altering User Mode Memory

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 AMD64
Copyright (c) Microsoft Corporation. All rights reserved.

Opened \\.\pipe\kd
Waiting to reconnect...
Connected to Windows 10 10240 x64 target at (Wed Jun 29 18:03:54.125 2016 (UTC - 7:00)), ptr64 TRUE
Kernel Debugger connection established.
Symbol search path is: srv*
Executable search path is: 
Windows 10 Kernel Version 10240 MP (4 procs) Free x64
Product: WinNt, suite: TerminalServer SingleUserTS
Built by: 10240.16841.amd64fre.th1_st1.160408-1853
Machine Name:
Kernel base = 0xfffff800`8f685000 PsLoadedModuleList = 0xfffff800`8f9aa070
Debug session time: Wed Jun 29 18:03:52.560 2016 (UTC - 7:00)
System Uptime: 0 days 4:34:42.939
Break 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)     Location
Deferred                                       cache*d:\sym
Deferred                                       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:\_inc\test\amd64
Symbol search path is: cache*d:\sym;srv*;D:\_inc\test\amd64
Expanded Symbol search path is: cache*d:\sym;SRV*https://msdl.microsoft.com/download/symbols;d:\_inc\test\amd64

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 17
PROCESS 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:\test\meason_test\main.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`e2818323
00007ff7`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 ffffe0016723c840
Implicit process is now ffffe001`6723c840
.cache forcedecodeuser done
Loading User Symbols
.....

************* Symbol Loading Error Summary **************
Module name            Error
msrpc                  The system cannot find the file specified
clipsp                 The system cannot find the file specified
spaceport              The system cannot find the file specified
volmgrx                The system cannot find the file specified
Fs_Rec                 The system cannot find the file specified
Null                   The system cannot find the file specified
peauth                 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:\test\meason_test\main.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:\test\meason_test\main.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:\test\amd64>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.exe
PROCESS 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 ffffe00138301840
Implicit process is now ffffe001`38301840
.cache forcedecodeuser done
Loading User Symbols
.....

Using !process, let's iterate over all threads in the process:

 0: kd> !process ffffe00138301840 f
PROCESS 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:\test\meason_test\main.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

Our process only has one thread, so choosing the relevant thread here is trivial.  Set the debugger context to that thread, so we can pull up its call stack:

 0: kd> .thread /p /r ffffe00138d9a640  
Implicit thread is now ffffe001`38d9a640
Implicit process is now ffffe001`38301840
.cache forcedecodeuser done
Loading User Symbols
.....
0: kd> k
  *** Stack trace for last set context - .thread/.cxr resets it
 # Child-SP          RetAddr           Call Site
00 ffffd001`5490d7d0 fffff801`8f4592a0 nt!KiSwapContext+0x76
01 ffffd001`5490d910 fffff801`8f458cb8 nt!KiSwapThread+0x160
02 ffffd001`5490d9c0 fffff801`8f489d89 nt!KiCommitThreadWait+0x148
03 ffffd001`5490da50 fffff801`8f8aecac nt!KeDelayExecutionThread+0x229
04 ffffd001`5490dad0 fffff801`8f55eb63 nt!NtDelayExecution+0x5c
05 ffffd001`5490db00 00007ffa`5a313b6a nt!KiSystemServiceCopyEnd+0x13
06 000000c5`afc5f7d8 00007ffa`57743757 ntdll!NtDelayExecution+0xa
07 000000c5`afc5f7e0 00007ff7`a5b98323 KERNELBASE!SleepEx+0xa7
08 000000c5`afc5f880 00007ff7`a5b9b96d meason_test!main+0x73 [d:\test\meason_test\main.cpp @ 13]
09 000000c5`afc5f8c0 00007ffa`57f42d92 meason_test!__mainCRTStartup+0x14d [(omitted) @ 697]
0a 000000c5`afc5f900 00007ffa`5a289f64 KERNEL32!BaseThreadInitThunk+0x22
0b 000000c5`afc5f930 00000000`00000000 ntdll!RtlUserThreadStart+0x34

We are going to alter the loop iteration variable, which is a variable stored on the stack.  It is located in the main() function, so let's switch debugger context to that frame of the call stack.  The frame number is 8, which we can see as the first column in the 'k' output above.

 0: kd> .frame 8
08 000000c5`afc5f880 00007ff7`a5b9b96d meason_test!main+0x73 [d:\test\meason_test\main.cpp @ 13]
0: kd> dv
              i = 0n27

How do you find the address of this stack variable?  There are many ways, but we'll use the most clunky method: Look at disassembly.  Let's look for the variable being incremented in the main function:

 0: kd> u meason_test!main
meason_test!main [d:\test\meason_test\main.cpp @ 6]:
00007ff7`a5b982b0 4883ec38        sub     rsp,38h
00007ff7`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,rax
00007ff7`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],0
0: kd> u
meason_test!main+0x2e [d:\test\meason_test\main.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     eax
00007ff7`a5b982e6 89442420        mov     dword ptr [rsp+20h],eax
00007ff7`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]

The 'inc eax' instruction is the one we're looking for.  As we can see in the instruction above the 'inc eax' instruction, the EAX register gets loaded from whatever is at address [rsp+20h].  That is, 0x20 offset from the RSP register for the frame.  We can't rely on the actual value in the RSP register anymore,  as it is now loaded with data that is relative to frame 0 of our call stack.  Using the 'k' output from above, we see the stack pointer for this frame is 000000c5`afc5f880.  Thus, we retrieve data at 0x20 offset from that address:

 0: kd> dd 000000c5`afc5f880+0x20
000000c5`afc5f8a0  0000001b 00000000 00000000 00000000
000000c5`afc5f8b0  00000000 00000000 a5b9b96d 00007ff7
000000c5`afc5f8c0  00000001 00000000 00000000 00000000
000000c5`afc5f8d0  00000000 00000000 a5b9d3d0 00007ff7
000000c5`afc5f8e0  00000000 00000000 a5b9d3d0 00007ff7
000000c5`afc5f8f0  00000000 00000000 57f42d92 00007ffa
000000c5`afc5f900  a5b9b9f0 00007ff7 a568f000 00007ff7
000000c5`afc5f910  a568f000 00007ff7 00000000 00000000
0: kd> ? 0000001b
Evaluate expression: 27 = 00000000`0000001b

 Notice how we used the 'dd' debugger command to dump 32-bit values, as our code is using a 32-bit data type.  As we can see, the loop iteration is at number 27 in our program's execution.

Let's alter the value to something wacky, just to be sure we edited the value we want.  Use the 'ed' debugger command to edit the value at the address of this stack variable.  Notice that this only works when the debugger context is set to this process, so the debugger knows how to translate this address properly.

 0: kd> ed 000000c5`afc5f880+0x20 0x12345678
0: kd> dd 000000c5`afc5f880+0x20
000000c5`afc5f8a0  12345678 00000000 00000000 00000000
000000c5`afc5f8b0  00000000 00000000 a5b9b96d 00007ff7
000000c5`afc5f8c0  00000001 00000000 00000000 00000000
000000c5`afc5f8d0  00000000 00000000 a5b9d3d0 00007ff7
000000c5`afc5f8e0  00000000 00000000 a5b9d3d0 00007ff7
000000c5`afc5f8f0  00000000 00000000 57f42d92 00007ffa
000000c5`afc5f900  a5b9b9f0 00007ff7 a568f000 00007ff7
000000c5`afc5f910  a568f000 00007ff7 00000000 00000000

Now, let's run the app and see what the result looks like.  Make sure to resume the debugger so the OS can start executing again.

 Loop iteration: 25
Loop iteration: 26
Loop iteration: 27
Loop iteration: 305419897
Loop iteration: 305419898
Loop iteration: 305419899

That first large value corresponds to 0x12345679, which is what we would expect after coming out of the Sleep() point of the app and starting on the next loop iteration.


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.