Since you asked.. How to obtain value of a field using WinDbg?

kao

My friends occasionally ask me tricky questions - about PE file format, about .NET internals, usage of dnlib or WinDbg and what not. Even if I'm not an expert in some areas, I usually can figure out stuff pretty fast.

So, here's a question that li0nsar3c00l asked me few days ago:

How to get the value of a static field using WinDbg?

To put the question in context - he is trying to debug a .NET assembly which is obfuscated and all names are unprintable. When all names look like "□", it's quite hard to find out which is which - and I doubt you can use those names when setting breakpoints.. So, we need to figure out a way that avoids using object names.

I will assume that you have very basic knowledge of using WinDbg with .NET applications. If you don't, I suggest that you start by reading introductory tutorials, for example, Getting started with Windbg - part I and part II. You could also check WinDbg tutorial by netmatze. Or just go through the entire amazing collection of .NET Debugging Demos by Tess Ferrandez.

Quick answer

You can use !mx command to locate the class and field you need. Then you can use !mdt -e command to display values you need.

But if you have no idea how those commands work, please continue reading..

Long answer

Dealing with static classes and static fields is easier. After the corresponding .cctor is executed, you can get the values you need. But for non-static classes and fields you need to stop debugger at a place where an instance of the object is already created and values initialized.

So, we'll take a scenic route from the very beginning till the end. Here's a demo app you can test your skills with: https://www.mediafire.com/?wavmalk6sqhdcy6

  1. Load sos and sosex extensions. I'm using this one-liner for .NET 4.0 - you can use separate commands, if you prefer.
  2. 0:000> sxe ld:clrjit;g;.loadby sos clr;.load F:\Sosex\sosex.dll
    *** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll - 
    (1810.15ac): Unknown exception - code 04242420 (first chance)
    ModLoad: 62620000 6268e000   C:\Windows\Microsoft.NET\Framework\v4.0.30319\clrjit.dll
    
  3. Examine domain, find module
  4. 0:000> !DumpDomain
    {boring stuff here}
    
    Assembly:           007894c8 [F:\ConsoleApplication1.exe]
    ClassLoader:        00789590
    SecurityDescriptor: 00788ca8
      Module Name
    00192e94    F:\ConsoleApplication1.exe
  5. Find the type you want
  6. 0:000> !DumpModule -mt 00192e94
    Name:       F:\ConsoleApplication1.exe
    Attributes: PEFile 
    Assembly:   007894c8
    LoaderHeap:              00000000
    TypeDefToMethodTableMap: 00190038
    TypeRefToMethodTableMap: 00190050
    MethodDefToDescMap:      001900bc
    FieldDefToDescMap:       001900d0
    MemberRefToDescMap:      001900e4
    FileReferencesMap:       00190140
    AssemblyReferencesMap:   00190144
    MetaData start address:  013120d8 (2160 bytes)
    
    Types defined in this module
    
          MT  TypeDef Name
    ------------------------------------------------------------------------------
    001937e0 0x02000003 ConsoleApplication1.Program
    
    Types referenced in this module
    
          MT    TypeRef Name
    ------------------------------------------------------------------------------
    62a8b064 0x02000001 System.Object
  7. Find the method you need
  8. 0:000> !DumpMT -md 001937e0 
    EEClass:         001912d0
    Module:          00192e94
    Name:            ConsoleApplication1.Program
    mdToken:         02000003
    File:            F:\ConsoleApplication1.exe
    BaseSize:        0xc
    ComponentSize:   0x0
    Slots in VTable: 6
    Number of IFaces in IFaceMap: 0
    --------------------------------------
    MethodDesc Table
       Entry MethodDe    JIT Name
    629949d0 62696728 PreJIT System.Object.ToString()
    62988800 62696730 PreJIT System.Object.Equals(System.Object)
    629883d0 62696750 PreJIT System.Object.GetHashCode()
    62981760 62696764 PreJIT System.Object.Finalize()
    0019c015 001937d8   NONE ConsoleApplication1.Program..ctor()
    0019c011 001937cc   NONE ConsoleApplication1.Program.Main(System.String[])
  9. Put breakpoint on the method and run
  10. 0:000> !bpmd -md 001937cc
    MethodDesc = 001937cc
    Adding pending breakpoints...
    0:000> g
    (1810.15ac): CLR notification exception - code e0444143 (first chance)
    JITTED ConsoleApplication1!ConsoleApplication1.Program.Main(System.String[])
    Setting breakpoint: bp 003F00B9 [ConsoleApplication1.Program.Main(System.String[])]
    Breakpoint: JIT notification received for method ConsoleApplication1.Program.Main(System.String[]) in AppDomain 00735ca8.
    Breakpoint 0 hit
    eax=003f00b0 ebx=0033f54c ecx=02722330 edx=00000000 esi=00000000 edi=0033f4c0
    eip=003f00b9 esp=0033f490 ebp=0033f4a8 iopl=0         nv up ei pl nz na pe nc
    cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
    003f00b9 b968381900      mov     ecx,193868h
  11. Disassemble code, locate place where the interesting object will be already initialized.
  12. You need to put a breakpoint after the object is created and initialized.

    0:000> !U eip
    Normal JIT generated code
    ConsoleApplication1.Program.Main(System.String[])
    Begin 003f00b0, size 8a
    *** WARNING: Unable to verify checksum for ConsoleApplication1.exe
    
    003f00b0 55              push    ebp
    003f00b1 8bec            mov     ebp,esp
    003f00b3 57              push    edi
    003f00b4 56              push    esi
    003f00b5 53              push    ebx
    003f00b6 83ec0c          sub     esp,0Ch
    >>> 003f00b9 b968381900      mov     ecx,193868h (MT: ConsoleApplication1.Bla)
    003f00be e83d20d9ff      call    00182100 (JitHelp: CORINFO_HELP_NEWSFAST)
    003f00c3 8bf8            mov     edi,eax
    003f00c5 8bcf            mov     ecx,edi
    003f00c7 ff15a8381900    call    dword ptr ds:[1938A8h] (ConsoleApplication1.Bla..ctor(), mdToken: 06000001)
    003f00cd b940bfa862      mov     ecx,offset mscorlib_ni+0x3fbf40 (62a8bf40) (MT: System.Byte)
    
    {boring stuff here}

    Unfortunately !U doesn't show IL code. So, let's use !mu output to confirm our findings:

    0:000> !mu
    *** WARNING: Unable to verify checksum for ConsoleApplication1.exe
        IL_0000: newobj ConsoleApplication1.Bla::.ctor()
        IL_0005: stloc.0  (bla)
            >>> 003f00b9 b968381900      mov     ecx,193868h
            003f00be e83d20d9ff      call    00182100
            003f00c3 8bf8            mov     edi,eax
            003f00c5 8bcf            mov     ecx,edi
            003f00c7 ff15a8381900    call    dword ptr ds:[1938A8h]
        IL_0006: ldstr "{0:X} {1:X}"
        IL_000b: ldsfld ConsoleApplication1.Bla::key1
        IL_0010: ldc.i4.1 
        IL_0011: ldelem.u1 
        IL_0012: box System.Byte
        IL_0017: ldloc.0  (bla)
        IL_0018: ldfld ConsoleApplication1.Bla::key2
        IL_001d: ldc.i4.2 
        IL_001e: ldelem.u1 
        IL_001f: box System.Byte
        IL_0024: call System.Console::WriteLine
        IL_0029: call System.Console::ReadKey
        IL_002e: pop 
            *** WARNING: Unable to verify checksum for C:\Windows\assembly\NativeImages_v4.0.30319_32\mscorlib\d12f4fda3d1bfabf888342e96983e9a7\mscorlib.ni.dll
    *** ERROR: Module load completed but symbols could not be loaded for C:\Windows\assembly\NativeImages_v4.0.30319_32\mscorlib\d12f4fda3d1bfabf888342e96983e9a7\mscorlib.ni.dll
    003f00cd b940bfa862      mov     ecx,offset mscorlib_ni+0x3fbf40 (62a8bf40)
    
    {boring stuff here}

    Command !mu doesn't show call targets but interleaves IL code with x86 code. Combining both outputs we know that we should get to address 003f00cd.

  13. Execute code to the place where data are already initialized
  14. 0:000> g 003f00cd 
    eax=ea1dad0b ebx=0033f54c ecx=027223a4 edx=00009c88 esi=00000000 edi=02722398
    eip=003f00cd esp=0033f490 ebp=0033f4a8 iopl=0         nv up ei pl zr na pe nc
    cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
    003f00cd b940bfa862      mov     ecx,offset mscorlib_ni+0x3fbf40 (62a8bf40)
  15. Use command !mx
  16. This command is little known but it's bloody awesome!

    Usage: !sosex.mx <Filter String>

    Displays any matching type, method or field for <Filter String>, where <Filter String> is a string in module!metadataname format. If module! is not specified, all modules are searched for the specified metadata. Searched info includes types, methods and fields.

    In order to search globals, do not precede the field or method filter with a ".". To enumerate all globals for a given module filter, use "globals" as the type filter. eg: "globals" "*!globals" "mymod!globals", etc...

    So, let's ask to show us all objects from our test application:

    0:000> !mx ConsoleApplication1!*
    AppDomain 63d78180 (Shared Domain)
    ---------------------------------------------------------
    
    AppDomain 00735ca8 (ConsoleApplication1.exe)
    ---------------------------------------------------------
    module: ConsoleApplication1
      class: ConsoleApplication1.Bla
        .ctor()
        static .cctor()
        static key1 {fieldtype: byte[]}
        key2 {fieldtype: byte[]} 
      class: ConsoleApplication1.Program
        static Main(string[])
        .ctor()
      class: {C5635AC4-C624-4BB2-B53A-AC963D1790A5}
        static $$method0x6000004-1 {fieldtype: __StaticArrayInitTypeSize=3} 
        static $$method0x6000001-1 {fieldtype: int} 
  17. Get the address of class
  18. Class Bla contains static field key1 which we're interested in. Click on Bla hyperlink. You actually don't care about the output, just the address of class.

    0:000> !dumpmt 00193868
    {lots of boring stuff}
  19. Use !mdt command to get address of the field
  20. 0:000> !mdt -e 00193868
    ConsoleApplication1.Bla
        [s]key1: byte[]
            AppDomain 'ConsoleApplication1.exe' (00735ca8): 02722340[System.Byte[]]
        key2: byte[]
  21. Use provided hyperlinks to access data
  22. Clicking on "02722340" and then on "Expand" will get you the good stuff. Or you can enter "!mdt" command manually:

    0:000> !mdt 02722340
    02722340 (System.Byte[], Elements: 3)  expand
    0:000> !mdt -e:1 02722340
    02722340 (System.Byte[], Elements: 3)  expand
    [0] 0x12
    [1] 0x23
    [2] 0x34

    Congratulations, this is the value of static field. Task #1 done! 🙂

  23. Locate instance of the class
  24. Remember, we put a breakpoint just after the class initialization. You can either use your x86 knowledge and check the proper register values, or just display managed stack objects:

    0:000> !dso
    OS Thread Id: 0x15ac (0)
    ESP/REG  Object   Name
    ecx      027223a4 System.Byte[]
    edi      02722398 ConsoleApplication1.Bla
    0033F498 02722330 System.Object[]    (System.String[])
    0033F520 02722330 System.Object[]    (System.String[])
    0033F67C 02722330 System.Object[]    (System.String[])
    0033F6B4 02722330 System.Object[]    (System.String[])
  25. Use !mdt command to get the stuff just like before
  26. 0:000> !mdt 02722398 
    02722398 (ConsoleApplication1.Bla)
        key2:027223a4 (System.Byte[], Elements: 4)  expand
    0:000> !mdt -e:1 027223a4
    027223a4 (System.Byte[], Elements: 4)  expand
    [0] 0x0b
    [1] 0xad
    [2] 0x1d
    [3] 0xea

    Congrats, this is value of non-static field. Task #2 done!

This concludes our journey. Hopefully you learned something new today!