Making an Image Easier to Debug


I am doing security review for a managed application which is obfuscated. So I am doing a lot of   disassembling code at runtime using Windbg. One of the issues is that code gets JIT optimized because of the retail build. This makes it harder for me debug when mapping it back. Realized  that I could turnoff  JIT Optimization’s using the ini file.

[.NET Framework Debugging Control]
GenerateTrackingInfo=1
AllowOptimize=0

Another use of feature which I guess wasn’t really intended for.


Updating .NET String in memory with Windbg


In this post I would show a simple trick to update .NET strings in memory with Windbg. The caveat is make sure the string that you’re updating is long enough to fit into the string buffer. If not there would be a memory corruption.

Here is a simple windows form application with title “Good”

The goal is to update the title from “Good” to “Bad”.


button1.Click += (s,b) => Text = _caption;

I am updating the title in the button click.

Here is the actual string object within the debugger

0:006> !do 0294d0a0
Name:        System.String
MethodTable: 59b9fb64
EEClass:     598d8bb0
Size:        22(0x16) bytes
File:        C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\
v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
String:      Good
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
59ba2b30  40000ed        4         System.Int32  1 instance        4 m_stringLength
59ba1f80  40000ee        8          System.Char  1 instance       47 m_firstChar
59b9fb64  40000ef        8        System.String  0   shared   static Empty
    >> Domain:Value  004b0308:02941228 <<

I would be using the e  command to update the memory. The ezu command is used for updating  Null-terminated Unicode string .

Notice the first character starts in the 8th offset from the above. So we would have start updating the string only from the 8th offset. The first 8 bytes of object are for syncblock index and method table pointer.

Here is the command to update the string memory.

ezu 0294d0a0+8 “Bad”

And the updated form title.

Conditional BreakPoint based on callstack within Windbg – .NET


Someone recently asked me “How to have a break-point on a method based on certain function in the call-stack?”

Here is the sample code to demonstrate this

using System;
using System.Threading.Tasks;
using System.Data.SqlClient;
namespace Test
{
    class Program
    {
        string connectionString = @"Data Source=.\sqlexpress;Initial Catalog=Tfs_Configuration;Integrated Security=True";
        public void Bar()
        {
            using (var c = new SqlConnection(connectionString))
            {
                c.Open();
                var command = new SqlCommand(@"update [tbl_AccessMapping] set [DisplayName] = @param", c);
                command.Parameters.Add(new SqlParameter("param", "Bar"));
                command.ExecuteNonQuery();
            }
        }
        public void Foo()
        {
            using (var c = new SqlConnection(connectionString))
            {
                c.Open();
                var command = new SqlCommand(@"update [tbl_AccessMapping] set [DisplayName] = @param", c);
                command.Parameters.Add(new SqlParameter("param", "Foo"));
                command.ExecuteNonQuery();
            }
        }
        static void Main(string[] args1)
        {
            var s = new Program();
            Parallel.For(0, 2, (i) => s.Bar());
            Parallel.For(0, 2, (i) => s.Foo());
            Console.Read();
        }
    }
}

The requirement is to have a break-point on “ExecuteNonQuery” but it should break only if it is invoked from “Foo” and not from “Bar”.

Launched the exe within windbg and loaded sos,sosex and set a bp on System.Data.SqlClient.SqlCommand.ExecuteNonQuery suing !mbm

And when the break-point hits the first time updated the bp using

bs 0  $$>a<”d:\Debuggersx86\ConditionalBP.txt” Foo

Here are the contents of ConditionalBP.txt

ad /q Contains
aS /c Contains .shell -ci "!CLRStack" FINDSTR $arg1
.block {
            .if ($spat("${Contains}","*${$arg1}*"))
                {
                 !CLRStack
                }
           .else
                { 
                g
                }
     }
ad /q Contains

Saving Dynamic Assembly in .NET 4.0 using Windbg


I recently had to debug a .NET 4.0 process which was loading the dependent assemblies using the AppDomain.AssemblyResolve event. The dependent assemblies were stored within the executable. I couldn’t disassemble the code to look for the dependent assembly because the exe was obfuscated. FYI the dynamic assembly cannot be saved using !SaveModule and here is the reason for this read the comments especially from Evian. Unlike psscor2.dll the sos for .NET 4.0 does not have a !dumpdynamicassembly with a save option.

Here is the sample code to demonstrate this.


using System;
using System.Reflection;
using TestLib;
namespace Test
{
 class Foo1
 {
 int[] s = new int[2];
 int v = 100;
 public Foo1()
 {
 Console.WriteLine(new Class1().Foo());
 }
 static void Main(string[] args1)
 {
 AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
 {
 String resourceName = "ConsoleApplication13." +new AssemblyName(args.Name).Name + ".dll";
 using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName))
 {
 Byte[] assemblyData = new Byte[stream.Length];
 stream.Read(assemblyData, 0, assemblyData.Length);
 return Assembly.Load(assemblyData);
 }
 };
 System.IO.File.Delete(@"C:\Users\naveen\Documents\Visual Studio 2010\Projects\ConsoleApplication13\bin\Debug\TestLib.dll");
 var s = new Foo1();
 Console.Read();
 }
 }
}

I knew the Assembly had to be loaded using  System.Reflection.Assembly.Load(Byte[]) ,so ended setting a break-point on the method using command !mbm *Assembly.Load* on the launch of the executable.

Here are the output of args and local variables for the above break-point

0:000> !mdv
Frame 0×0: (System.Reflection.Assembly.Load(Byte[])):
[A0]:rawAssembly:0x025fc374 (System.Byte[])
[L0]:<?>

Notice the “rawAssembly” argument which has the assembly contents.  Here are the raw memory contents of the address using dd 0x025fc374

0:000> dd 0x025fc374
025fc374  6a764994 00001000 00905a4d 00000003
025fc384  00000004 0000ffff 000000b8 00000000
025fc394  00000040 00000000 00000000 00000000
025fc3a4  00000000 00000000 00000000 00000000
025fc3b4  00000000 00000080 0eba1f0e cd09b400
025fc3c4  4c01b821 685421cd 70207369 72676f72
025fc3d4  63206d61 6f6e6e61 65622074 6e757220
025fc3e4  206e6920 20534f44 65646f6d 0a0d0d2e

  1. 6a764994 :- Is the Array’s  Method Table
  2. 00001000 : – Is the Array size
  3. The rest are the array contents.

Unlike the reference type arrays, the value type arrays  don’t have a DWORD for Method table of its contents. With this information I could dump the contents from memory in to disk using .writemem command.


.writemem c:\temp\assembly.bin @ecx+8 L?(poi(@ecx+@$ptrsize)*@$ptrsize)

In x86 @ecx register contains argument for rawAssembly. The  @ecx+8 is the start  position of the first byte and that is the reason for using this as the start position for .writemem. The poi(@ecx+@$ptrsize) contains the array size which in our case is 0001000 and multiply it by @$ptrsize which is 4 in x86. The expression (poi(@ecx+@$ptrsize)*@$ptrsize) would in our case result to 4000 bytes.

The assembly.bin would contain data in hex format which has to be converted in to binary format. Here is the code to convert from Hex to Binary format.

Assembly.Load( File.ReadAllBytes(@"c:\temp\assembly.bin")
 .Select(x =>
 Convert.ToByte(
 int.Parse((x.ToString("X")),NumberStyles.HexNumber)
 )).ToArray())
.FullName.Dump();

Dumping Generic List in .NET within Windbg


Most of the code uses List<T> for storing items.  The present solutions don’ t have a way to dump List<T> within windbg. Even though sosex has an option to dump the List<T> using !mdt it still doesn’t meet the scripting requirements. For example here is an output using sosex “!mdt -e 029a91c0″

0:000> !mdt -e 029a91c0
029a91c0 (System.Collections.Generic.List`1[[Test.Foo, Test]])
Count = 2
[0] 029a9200 (Test.Foo)
[1] 029a9210 (Test.Foo)

I would have preferred to get the contents of the “Foo” object instead of just the address of Foo. So wrote a script to do that.


$$ pointer to the array within the List
r @$t5 = poi(${$arg1}+@$ptrsize)

 .if (@$ptrsize = 8 )
 {
    r @t7 = 20 
 } 
 .else 
 { 
    r @$t7 = 10
 }

 .for (r $t0=0; @$t0 &lt; poi(@$t5+@$ptrsize); r$t0=@$t0+1 )
 {
     .if(@$t0 = 0)
     {
         $$ First occurence of the element in the array would be in the 20 offset for x64 and 10 offset for x86
         r$t1=@$t7
     }
     .else
     {
         $$ the rest of the elements would be in the 8th offset for x64 and 4th offset for x86
         r$t1= @$t7+(@$t0*@$ptrsize)
     }
     $$ Check for null before trying to dump
     .if (poi((@$t5-@$ptrsize)+@$t1) = 0 )
     {
     .continue
     }
     .else
     {
     .printf &quot;%N \n&quot; ,poi((@$t5-@$ptrsize)+@$t1)
     }
 }                                  

This script should work in x86 and x64. To use the above script copy to a file and invoke it like this passing the address of List<T>

$$>a<"d:\Debuggersx86\dumplist.txt" 029a91c0

Here is the output from the above command.

0:000> $$>a<"d:\Debuggersx86\dumplist.txt" 029a91c0
029A9200
029A9210

Now with this script I can use !mdt to get the contents of the “Foo” object.

.foreach ($obj {$$>a<"d:\Debuggersx86\dumplist.txt" 029a91c0}) {!mdt $obj}

0:000> .foreach ($obj {$$>a<"d:\Debuggersx86\dumplist.txt" 029a91c0}) {!mdt $obj}
029a9200 (Test.Foo)
counter:0×1 (System.Int32)
Name:029a917c (System.String: "test")
029a9210 (Test.Foo)
counter:0×2 (System.Int32)
Name:029a9198 (System.String: "test2")

This is one of the scripts that I would use often. Hope it is useful to others also.

Why isn’t the !bpmd in sos / windbg not working?


I recently noticed another blog post refer to one of my post. The issue was, sos wasn’t enabling the break-points on non-jitted functions. The classic example being “Main”.  Thanks to Steve I have been using sosex and not sos for setting break-points.

From my previous post you can understand how CLR is using clrn/CLRNotificationException to notify sos/sosex on JIT. With this information when I looked at the rotor code, I noticed an interesting member variable “g_dacNotificationFlags”. So I decided to check the value of this variable when using !bpmd from sos and !mbm from sosex.


.if (dwo(mscorwks!g_dacNotificationFlags) = 0) {.echo bp not set } .else {.echo bp set}

It was “0″ when using sos and “1″ when using sosex. Now I had to change the value to “1″ and check if the break-point becomes active when using sos’s !bpmd.  FYI I don’t have private symbols and haven’t seen CLR Code. Here is the code to set the value to “1″.


ed mscorwks!g_dacNotificationFlags 00000001

And not to my surprise the !bpmd seems to work for non-jitted function with the above hack. FYI we don’t have to resort to this to get !bpmd to work. If the !bpmd is set after load of mscorjit/clrjit it would work as expected.

Decoding clr20r3 .NET exception – using mono cecil


I have often seen Devs trying to figure out the cause of the app crash without a memory dump. The only information that is available to analyze is the Windows Error Reporting message in the event viewer which would have “Event Name: CLR20r3″ along with Watson bucket information like this.

Fault bucket , type 0
Event Name: CLR20r3
Response: Not available
Cab Id: 0

Problem signature:
P1: unhandledexception.exe
P2: 1.0.0.0
P3: 4ce1e0f1
P4: LibraryCode
P5: 1.0.0.0
P6: 4ce1e0f1
P7: 7
P8: 1f
P9: System.NullReferenceException
P10:

I will demonstrate the steps in identifying the code that caused the app to crash with the above information.Here is the explanation on the Watson Bucket items

  1. P1: unhandledexception.exe – is the Exe File Name
  2. P2:1.0.0.0 – is the Exe File assembly version number
  3. P3:4ce1e0f1- is the Exe File Stamp
  4. P4:LibraryCode- is the Faulting full assembly name
  5. P5:1.0.0.0- is the Faulting assembly version
  6. P6:4ce1e0f1- is the Faulting assembly timestamp
  7. P7:7- is the Faulting assembly method def
  8. P8:1f-  is Faulting method IL Offset within the faulting method
  9. P9:System.NullReferenceException- is Exception type that was thrown

 

Here is the LibraryCode that is mentioned in P4 of the watson bucket

using System;

namespace LibraryCode
{
    public class Foo
    {
        public Foo()
        {
            Console.WriteLine("Constructor");
        }
        public void Test()
        {
            Console.WriteLine("Test");
        }
        public string Bar(string test)
        {
            var x = test;
            return x.ToUpper();
        }
        public string Bar1(string test)
        {
            var x = test;
            return x.ToUpper();
        }
        public string Bar2(string test)
        {
            var x = test;
            return x.ToUpper();
        }
        public string Bar3(string test)
        {
            var x = test;
            return x.ToUpper();
        }
        public string Bar4(string test)
        {
            int j = 10;
            for (int i = 0; i < 10; i++)
            {
                j += i;
            }
            var x = test;
            return x.ToUpper();
        }
    }
}


And here is the code for the Main method calling the LibraryCode

  static void Main(string[] args)
        {
            var f = new Foo();
            var x = Console.ReadKey();
            f.Bar4(null);
        }

The most important items in the above watson bucket are 4,7 ,8 and 9. The item 4 is the assembly that was responsible for the crash which is “LibraryCode”. The item 7 is methoddef that threw the exception which is “7″. To identify the method we would have to dump the IL and here is the command to do that.

ildasm /tokens "C:\temp\LibraryCode.dll" /out=libcode.il

Open the libcode.il in a text editor and look for 06000007. The methoddef starts with 06 and 7 is the hex value and when converted to decimal it is still 7 and that’s how we ended with 06000007. The IL content for the corresponding method def

.method /*06000007*/ public hidebysig instance string
Bar4(string test) cil managed
{
// Code size       42 (0x2a)

With this we know the method that caused the app to crash.

The next step is to identify the faulting IL code within the method. The IL offset that caused the exception to be thrown is 1f (decimal value is 31), and here is the IL Code

IL_001d:  ldarg.1
IL_001e:  stloc.2
IL_001f:  ldloc.2
IL_0020:  callvirt   instance string [mscorlib/*23000001*/]System.String/*01000013*/::ToUpper() /* 0A000012 */
IL_0025:  stloc.3
IL_0026:  br.s       IL_0028

Now mapping the IL code back to C# shouldn’t be hard.

And If you are like me then you would probably want to automate things , so here is doing the same using Mono Cecil

AssemblyFactory.GetAssembly(@"C:\Temp\LibraryCode.dll")
		.MainModule.Types.Cast<TypeDefinition>()
		.ElementAt(1)
		.Methods.Cast<MethodDefinition>().First(md => md.MetadataToken.RID == 7)
		.Body.Instructions.Cast<Instruction>()
		.Select (i => 
			new {Offset = i.Offset, 
			OpCode = i.OpCode.ToString() , 
			Operand = i.Operand != null ? i.Operand.ToString() : string.Empty} )
		.Dump();

Notice the above code looks for methoddef “7″ which is the P7 item in the Watson bucket.The code could have just dumped 31st IL offset which is “ldloc.2″ but that would not help , I like to see the entire method to figure out the cause of the exception.

And here is the output from above code.

We cannot get the call-stack for the crash with just watson buckets.

Downloading PDC10 videos using the new async feature


I knew PDC10 has an OData endpoint which is http://odata.microsoftpdc.com/ODataSchedule.svc/ . The best part about  OData is querying for specific data that we are looking for. And here is my OData url for filtering twitter hashtag #languages


http://odata.microsoftpdc.com/ODataSchedule.svc/Sessions()?$filter=startswith(TwitterHashtag,'%23languages')&$expand=DownloadableContent&$select=DownloadableContent

With the above OData feed I could get urls for low bandwidth mp4′s that I can download. And here is the sample code for filtering


var x =XDocument.Load(@"c:\temp\session.xml").Descendants().AsParallel().Where(xd => xd.Name.LocalName=="Url"
&& xd.Value.Contains("_Low.mp4")).Select (xd => xd.Value);

Now that I have the url’s ,here is the code to download the videos using the new async feature

using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using System.Xml.Linq;

namespace Test
{
 class Foo
 {
 static void Main(string[] args)
 {
 DownloadAsync();
 Console.Read();
 }
 static async void DownloadAsync()
 {
 var result = new WebClient().DownloadStringTaskAsync("http://odata.microsoftpdc.com/ODataSchedule.svc/Sessions()?$filter=startswith(TwitterHashtag,'%23languages')&$expand=DownloadableContent&$select=DownloadableContent");
 var downloads = XDocument.Parse(await result).Descendants().AsParallel().
 Where(xd => xd.Name.LocalName == "Url" && xd.Value.Contains("_Low.mp4")).
 Select(xd => new WebClient().DownloadFileTaskAsync(xd.Value, Path.GetFileName(xd.Value)));
 await TaskEx.WhenAll(downloads).ContinueWith(_ => Console.WriteLine("Downloading Complete"));
 }
 }
}

Dumping .NET strings to files using Windbg


In this post I would demonstrate how to dump strings from a memory dump /live process to a file. Recently I had to debug a process which had few big strings where I had to analyze its contents. The !dumpobj from sos would only dump partial strings.  I had to dump few hundred XML strings that I had to analyze using some automation. And hence comes the script.

$$ Dumps the managed strings to a file 
$$ Platform x86
$$ Naveen Srinivasan http://naveensrinivasan.com
$$ Usage $$>a<"c:\temp\dumpstringtofolder.txt" 6544f9ac 5000 c:\temp\stringtest 
$$ First argument is the string method table pointer 
$$ Second argument is the Min size of the string that needs to be used filter
$$ the strings
$$ Third is the path of the file 
.foreach ($string {!dumpheap -short -mt ${$arg1}  -min ${$arg2}}) 
{ 

  $$ MT        Field      Offset               Type  VT     Attr    Value Name
  $$ 65452978  40000ed        4         System.Int32  1 instance    71117 m_stringLength
  $$ 65451dc8  40000ee        8          System.Char  1 instance       3c m_firstChar
  $$ 6544f9ac  40000ef        8        System.String  0   shared   static Empty
 
  $$ start of string is stored in the 8th offset, which can be inferred from above
  $$ Size of the string which is stored in the 4th offset
  r@$t0=  poi(${$string}+4)*2
  .writemem ${$arg3}${$string}.txt ${$string}+8 ${$string}+8+@$t0
}

And to use the above script ,copy it to a file and invoke it within Windbg/cdb

$$>a<”c:\temp\dumpstringtofolder.txt” 6544f9ac 5000 c:\temp\stringtest

Parameters to the script

  1. 6544f9ac :- Is the MT to string.
  2. 5000 :- Is the min size of the string that I want to dump
  3. c:\temp\stringtest :- Is the path along with partial filename for each string item

The dumped contents would be in Unicode format and to view its contents use something like this


Console.WriteLine(ASCIIEncoding.Unicode.GetString(File.ReadAllBytes(@"c:\temp\stringtest03575270.txt")));

And here is a sample code that downloads big xml strings ,that can be used by the above script to dump its contents to a folder


using System;
using System.Net;
namespace Test
{
    class Program
    {
        static void Main(string[] args)
        {
            var speakers = new WebClient().DownloadString("http://www.codemash.org/rest/speakers");
            var sessions = new WebClient().DownloadString("http://www.codemash.org/rest/sessions");
            Console.Read();
        }
    }
}

Dumping ASP.NET Session (x86 /x64) within Windbg


This post is going to be about dumping ASP.NET session objects using Windbg. I had recently answered a stackoverflow question in which someone wanted to dump ASP.NET session objects for 64-bit IIS (x64). I thought why not blog about the same which might be useful to others.  The challenge is to write one script that should work in both x86/x64.  FYI there is a script from Tess that does dump out the session contents, AFAIK it will not work on x64 and my script iterates through the array using the array length instead of using “.foreach /pS 2 /ps 99” which is somewhat cleaner.

Here is the script for dumping ASP.NET session objects within Windbg / CDB


$$$ Dump the ASP.NET Session objects within windbg/cdb
$$$ Platform : x86 / x64
$$$ Naveen Srinivasan  http://naveensrinivasan.com
$$$ Usage: $$>a<"c:\Debuggersx86\dumpsession.txt" 000007fef4115c20
$$$ where 000007fef4115c20 is the MethodTable pointer System.Web.SessionState.HttpSessionState

r @$t9 = @$ptrsize
$$ $t9 register contains pointer size
$$ $t8 register contains the next offset of the variable
$$ $t7 register contains array start address

.if (@$ptrsize = 8 )
{
 $$$ x64
 r @$t8 = 10
 r @$t7 = 20
 r @$t6 = 10
}
.else
{
 $$$ x86
 r @$t8 = 6
 r @$t6 = 8
 r @$t7 = 10
}
.foreach ($obj {!dumpheap -mt ${$arg1} -short})
{
 $$ The !dumpheap -short option has last result as --------------- and
 $$ this .if is to avoid this
 .if ($spat ("${$obj}","------------------------------"))
 {}
 .else
 {
 $$ $t5 contains refernce to the array which has key and value for the
 $$ session contents

 r$t5 = poi(poi(poi(poi(${$obj}+@$t9)+@$t6)+@$t9)+@$t9)
 $$$ Iterating through the array elements
 .for (r $t0=0; @$t0 < poi(@$t5+@$t9); r$t0=@$t0+1 )
 {
 .if(@$t0 = 0)
 {
 $$ First occurence of the element in the array would be in the 20 offset for x64 and 10 offset for x86
 r$t1=@$t7
 }
 .else
 {
 $$ the rest of the elements would be in the 8th offset for x64 and 4th offset for x86
 r$t1= @$t7+(@$t0*@$t9)
 }
 $$ Check for null before trying to dump
 .if (poi((@$t5-@$t9)+@$t1) = 0 )
 {
 .continue
 }
 .else
 {
 .echo ************
 $$ Session Key
 .printf /ow "Session Key is :- "; !ds poi(poi((@$t5-@$t9)+@$t1)+@$t9)
 $$ Session value
 .printf /ow "Session value is :- ";!ds poi(poi((@$t5-@$t9)+@$t1)+@$t6)
 }
 }
 }
}

Copy the above script in to a file and invoke the script like this within Windbg

$$>a<”c:\Debuggersx86\dumpsession.txt” 000007fef4115c20

Passing the MT of System.Web.SessionState.HttpSessionState as the script argument.

Within the script I am using the alias !ds for dumping strings instead of using !dumpobj.
To create the alias use this command in x64

as !ds .printf "%mu \n", 10+

and in x86

as !ds .printf "%mu \n", C+

Replace !ds with !do for dumping regular objects instead of strings.

Here is the output from the above script

0:022> $$>a<”c:\Debuggersx86\dumpsession.txt” 000007fef4115c20
************
Session Key is :- Name
Session value is :- Test
************
Session Key is :- Name1
Session value is :- Test1

If you are only interested in getting the session contents the above script should get you the answer you are looking for. The rest of the post is an explanation of how the script works.

I am going to start by explaining one of the important statement in the script “r$t5 = poi(poi(poi(poi(${$obj}+@$t9)+@$t6)+@$t9)+@$t9)” which gets the contents of the array that contains the session key and value.

The $obj is the loop variable that contains the object address for each Http Session object  “.foreach ($obj {!dumpheap -mt ${$arg1} -short})”

If I dump the Http session object using !do

0:022> !do 000000013fe20c30
Name: System.Web.SessionState.HttpSessionState
MethodTable: 000007fef4115c20
EEClass: 000007fef3d73e00
Size: 24(0×18) bytes
(C:\Windows\assembly\GAC_64\System.Web\2.0.0.0__b03f5f7f11d50a3a\System.Web.dll)
Fields:
MT    Field   Offset                 Type VT     Attr            Value Name
000007fef40b59e8  4001f59        8 …IHttpSessionState  0 instance 000000013fe20bc0 _container

We can see the 8th offset contains the pointer to the “_container” object in x64 and in x86 it will be the 4th offset and that’s the reason we use poi(${$obj}+@$t9) which should work for both x86 and x64 because the value of @$t9 is the pointer size which will be 4 in x86 and 8 in x64.

The next step is to dump the “_container” which is equal to poi(${$obj}+@$t9)

0:022> !do poi(000000013fe20c30+8)
Name: System.Web.SessionState.HttpSessionStateContainer
MethodTable: 000007fef411e868
EEClass: 000007fef3d77348
Size: 64(0×40) bytes
(C:\Windows\assembly\GAC_64\System.Web\2.0.0.0__b03f5f7f11d50a3a\System.Web.dll)
Fields:
MT    Field   Offset                 Type VT     Attr            Value Name
000007fef7b77a80  4001f5a        8        System.String  0 instance 0000000000000000 _id
000007fef4086508  4001f5b       10 …ateItemCollection  0 instance 000000013fe20458 _sessionItems
000007fef4115390  4001f5c       18 …ObjectsCollection  0 instance 000000013fe209d0 _staticObjects
000007fef7b7ecf0  4001f5d       28         System.Int32  1 instance               20 _timeout
000007fef7b76c50  4001f5e       34       System.Boolean  1 instance                1 _newSession
000007fef411f8c8  4001f5f       2c         System.Int32  1 instance                1 _cookieMode
000007fef411f798  4001f60       30         System.Int32  1 instance                1 _mode
000007fef7b76c50  4001f61       35       System.Boolean  1 instance                0 _abandon
000007fef7b76c50  4001f62       36       System.Boolean  1 instance                0 _isReadonly
000007fef411e7b0  4001f63       20 …essionStateModule  0 instance 000000013fce1bd8 _stateModule

Now that we have the SessionContainer, we would have to get the contents of “_sessionItems” which is in the 10th offset in x64.

Next step is to dump “_sessionitems” using !do poi(poi(000000013fe20c30+8)+10) and this is equal to poi(poi(${$obj}+@$t9)+@$t6).In the starting of the script @$t6 is set to 10 or 8 based on platform.

0:022> !do poi(poi(000000013fe20c30+8)+10)
Name: System.Web.SessionState.SessionStateItemCollection
MethodTable: 000007fef4086650
EEClass: 000007fef3d2fcf0
Size: 112(0×70) bytes
(C:\Windows\assembly\GAC_64\System.Web\2.0.0.0__b03f5f7f11d50a3a\System.Web.dll)
Fields:
MT    Field   Offset                 Type VT     Attr            Value Name
000007fef7b76c50  400117b       44       System.Boolean  1 instance                0 _readOnly
000007fef7b7e968  400117c        8 …ections.ArrayList  0 instance 000000013fe20830 _entriesArray
000007fef7b7fd88  400117d       10 …IEqualityComparer  0 instance 000000013fc6b270 _keyComparer
000007fef7b7f3d8  400117e       18 …ections.Hashtable  0 instance 000000013fe20858 _entriesTable
000007fef6f6f938  400117f       20 …e+NameObjectEntry  0 instance 0000000000000000 _nullKeyEntry
000007fef6f479b8  4001180       28 …se+KeysCollection  0 instance 0000000000000000 _keys
000007fef7b66840  4001181       30 …SerializationInfo  0 instance 0000000000000000 _serializationInfo
000007fef7b7ecf0  4001182       40         System.Int32  1 instance                3 _version
000007fef7b77370  4001183       38        System.Object  0 instance 0000000000000000 _syncRoot
000007fef7bbd028  4001184      a70 …em.StringComparer  0   shared           static defaultComparer
>> Domain:Value  00000000010e2690:NotInit  0000000002e0a0a0:00000000ffae8cb8 <<
000007fef7b76c50  4001f67       45       System.Boolean  1 instance                1 _dirty
000007fef4108b20  4001f68       48 …n+KeyedCollection  0 instance 0000000000000000 _serializedItems
000007fef7b7aa30  4001f69       50     System.IO.Stream  0 instance 0000000000000000 _stream
000007fef7b7ecf0  4001f6a       60         System.Int32  1 instance                0 _iLastOffset
000007fef7b77370  4001f6b       58        System.Object  0 instance 000000013fe20818 _serializedItemsLock
000007fef7b7f3d8  4001f66     18e0 …ections.Hashtable  0   shared           static s_immutableTypes
>> Domain:Value  00000000010e2690:NotInit  0000000002e0a0a0:000000013fe204c8 <<

Next field that we are interested in is “_entriesArray” which is in the 8th offset in x64. To dump its contents here is the command !do
poi(poi(poi(000000013fe20c30+8)+10)+8) which is equal to poi(poi(poi(${$obj}+@$t9)+@$t6)+@$t9

0:022> !do poi(poi(poi(000000013fe20c30+8)+10)+8)
Name: System.Collections.ArrayList
MethodTable: 000007fef7b7e968
EEClass: 000007fef7781ee0
Size: 40(0×28) bytes
(C:\Windows\assembly\GAC_64\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
Fields:
MT    Field   Offset                 Type VT     Attr            Value Name
000007fef7b65870  400094c        8      System.Object[]  0 instance 000000013fe3ddb8 _items
000007fef7b7ecf0  400094d       18         System.Int32  1 instance                2 _size
000007fef7b7ecf0  400094e       1c         System.Int32  1 instance                2 _version
000007fef7b77370  400094f       10        System.Object  0 instance 0000000000000000 _syncRoot
000007fef7b65870  4000950      388      System.Object[]  0   shared           static emptyArray
>> Domain:Value  00000000010e2690:00000000ffac6110 0000000002e0a0a0:00000000ffad19e0 <<

The next field we are interested  is “_items” which is in the 8th offset in x64. Notice “_items” is an array and cannot be dumped using !dumpobj or !do. So this command “r$t5 = poi(poi(poi(poi(${$obj}+@$t9)+@$t6)+@$t9)+@$t9)” will set the array pointer to$t5.

Now that we have array containing the session items, we could have used !da to
dump the array contents with details using !da -details poi(poi(poi(poi(000000013fe20c30+8)+10)+8)+8)

0:022> !da -details poi(poi(poi(poi(000000013fe20c30+8)+10)+8)+8)
Name: System.Object[]
MethodTable: 000007fef7b65870
EEClass: 000007fef777eb58
Size: 64(0×40) bytes
Array: Rank 1, Number of elements 4, Type CLASS
Element Methodtable: 000007fef7b77370
[0] 000000013fe3dd98
Name: System.Collections.Specialized.NameObjectCollectionBase+NameObjectEntry
MethodTable: 000007fef6f6f938
EEClass: 000007fef6ce90b0
Size: 32(0×20) bytes
(C:\Windows\assembly\GAC_MSIL\System\2.0.0.0__b77a5c561934e089\System.dll)
Fields:
MT    Field   Offset                 Type VT     Attr            Value Name
000007fef7b77a80  4001185        8        System.String  0 instance 000000013fe3dcf8 Key
000007fef7b77370  4001186       10        System.Object  0 instance 000000013fe3dd20 Value
[1] 000000013fe3ddf8
Name: System.Collections.Specialized.NameObjectCollectionBase+NameObjectEntry
MethodTable: 000007fef6f6f938
EEClass: 000007fef6ce90b0
Size: 32(0×20) bytes
(C:\Windows\assembly\GAC_MSIL\System\2.0.0.0__b77a5c561934e089\System.dll)
Fields:
MT    Field   Offset                 Type VT     Attr            Value Name
000007fef7b77a80  4001185        8        System.String  0 instance 000000013fe3dd48 Key
000007fef7b77370  4001186       10        System.Object  0 instance 000000013fe3dd70 Value
[2] null
[3] null

But notice it does not help much because we still cannot see the actual key and value. That is the reason for using a nested “.for” loop in the script which will iterate through the array contents. FYI @$t5 contains reference to the array.

The statement  “.for (r $t0=0; @$t0 < poi(@$t5+@$t9); r$t0=@$t0+1 )”  is standard for loop with one thing that is special which is poi(@$t5+@$t9).  The poi(@$t5+@$t9) contains the reference to the size of the array. How do I know that? The answer is dd poi(poi(poi(poi(000000013fe20c30+8)+10)+8)+8)

0:022> dd poi(poi(poi(poi(000000013fe20c30+8)+10)+8)+8)
00000001`3fe3ddb8  f7b65870 000007fe 00000004 00000000
00000001`3fe3ddc8  f7b77370 000007fe 3fe3dd98 00000001
00000001`3fe3ddd8  3fe3ddf8 00000001 00000000 00000000
00000001`3fe3dde8  00000000 00000000 00000000 00000000
00000001`3fe3ddf8  f6f6f938 000007fe 3fe3dd48 00000001
00000001`3fe3de08  3fe3dd70 00000001 00000000 00000000
00000001`3fe3de18  f7b77370 000007fe 00000000 00000000
00000001`3fe3de28  00000000 80000000 f7b77a80 000007fe

Notice the 8th offset value is 00000004 which is the size of the array and for
more information look at the post on custom dump array http://naveensrinivasan.com/2010/06/24/custom-dumparray-windbg/

The first element in the array  would be in the 20th offset in x64 and 10th
offset in x86 and that is the reason for the “.if(@$t0=0)”

.if(@$t0 = 0)
{
$$ First occurence of the element in the array would be in the 20 offset for x64 and 10 offset for x86
r$t1=@$t7
}

So the first time the value @$t1 would be 20. And the rest of the elements would be in the 8th offset in x64 and 4th offset inx86

.else
{
$$ the rest of the elements would be in the 8th offset for x64 and 4th offset for x86
r$t1= @$t7+(@$t0*@$t9)
}

So the second time it would be 20+(1*8) = @$t7+(@$t0*@$t9) which will be 28th offset.

The next statement “.if (poi((@$t5-@$t9)+@$t1) = 0 )” is null check , this would avoid dumping an object which has not be initialized. This is because not all elements in the array could have been initialized.

The !ds poi(poi((@$t5-@$t9)+@$t1)+@$t9) gets the session key which is in the 8th offset (look at the previous output from dumparray) and the !ds poi(poi((@$t5-@$t9)+@$t1)+@$t6) gets the session value which is in the 10th offset.

Here is my initial x64 specific script that I wrote.

foreach ($obj {!dumpheap -mt ${$arg1} -short})
{
$$ The !dumpheap -short option has last result as --------------- and
$$ this .if is to avoid this
.if ($spat ("${$obj}","------------------------------"))
{}
.else
{
$$ $t5 contains reference to the array which has key and value for the
$$ session contents

r$t5 = poi(poi(poi(poi(${$obj}+0x8)+0x10)+0x8)+0x8);
r$t1 = 0
.for (r $t0=0; @$t0 < poi(@$t5+0x8); r$t0=@$t0+1 )
{
.if(@$t0 = 0)
{
$$ First occurrence of the element in the array would be in the 20 offset
r$t1=20
}
.else
{
$$ the rest of the elements would be in the 8th offset
r$t1= 20+(@$t0*8)
};

$$ Check for null before trying to dump

.if (poi((@$t5-0x8)+@$t1) = 0 )
{
.continue
}
.else
{
.echo ************;
? @$t0
$$ Session Key
.printf "Session Key is :- "; !ds poi(poi((@$t5-0x8)+@$t1)+0x8);
$$ Session value
.printf "Session value is :- ";!ds poi(poi((@$t5-0x8)+@$t1)+0x10)
}
}
}
}

I had fun writing this script. Let me know if there is a better way to write this.

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: