Freigeben über


The Art of Guessing

Often when debugging, we’re presented with a problem scenario where our application specific knowledge is limited. This could mean we don’t have access to the source code or even that we do have access to the source code but simply don’t have time to read millions of lines of code to completely grasp the situation at hand.

In this type of scenario, it’s generally not immediately clear what steps we should take for a resolution. This means we often have to guess. Guessing is somewhat of a misnomer in this situation. I’m not advocating random guessing by any means, but rather taking a logical approach to guessing. When selecting an action based on guess work, you should be able to explain each step along the way. This means we need a way to guess well.

 The key to guessing well is leveraging what we do know about a system to in an attempt to make some logical assumptions to fill in the gaps of information that we don’t know. These are still guesses, but they’re logical guesses based on reason and sound thinking.

Below is a simple case study of a guess the number game written in Silverlight. The goal of the case study is not to demonstrate how to hack a guess the number game, but rather to illustrate the reasoning behind the guesswork needed to beat the game without relying on…well, guessing. 

The game works by prompting the user to guess a number and click a button. If the user guesses incorrectly, they see the following screen:

Our goal is to figure out what number to enter in order to guess correctly. To start with, we want to aggregate what we know about the application:

  1. The user enters a number into a text box and clicks a button.
  2. The number is likely evaluated against something in the application and if they match, we expect to get a message indicating success. Currently we are trying randomly and only getting failures.

Next we have to figure out what we want to know.

  1. What happens if the user enters the correct value?
  2. How was the application created and where is it hosted?
  3. How can we figure out what the correct value is?

With these questions, the logical first step is to talk to one of the developers that support the project. We’ll assume we got the following back:

  1. What happens if the user enters the correct value? The documentation says the label at the bottom should read, “You chose wisely.”
  2. How was the application created and where is it hosted? All I know is that it’s a Silverlight application that is viewed through the browser.
  3. How can we figure out what the correct value is? I have no idea. The code is all on Tommy’s computer and Tommy is on vacation in Jamaica.

Ok, we have a couple of good answers now. We’ve expanded what we know about the application. Now it’s time to take some action. Since the developer told us this is a Silverlight application hosted in a browser we also know that to debug this we can connect a debugger to the browser.

So, we hook up to the browser, and now we have to decide what to look for. We don’t know much about how the application works. We don’t know where the application stores the correct value. We don’t know enough about the application to know what method does the comparison of values. We don’t even know if the comparison code is hosted locally or if a call is made to a server.

That’s a lot of things we don’t know, so let’s go back to what we do know. We know that to kick off the comparison operation the user has to click a button. We also know this is a Silverlight application. We can guess that the button will be a Silverlight button.

This means we have a first step. We can use the debugger to search for the button in the managed heap.

First step is to load SOS for Silverlight:

0:038> .loadby sos coreclr

With SOS loaded, we can look on the managed heap for a button object. We might not know going into to it exactly what type is needed to create a Silverlight button, but we can guess that it probably contains the word “Button.”

0:038> !dumpheap -stat -type Button

Statistics:

      MT Count TotalSize Class Name

50878ec8 1 32 MS.Internal.FXCallbackDelegates+PasswordBox_AttachRevealButtonListenerDelegate

50878c1c 1 32 MS.Internal.FXCallbackDelegates+TextBox_AttachDeleteButtonClickHandlerDelegate

508ad1c0 1 128 System.Windows.Controls.Button

Total 3 objects

 

System.Windows.Controls.Button looks promising. There is only 1 of them and it ‘feels’ right. Next step is to find the address for that type:

0:038> !dumpheap -mt 508ad1c0       

 Address MT Size

0c862790 508ad1c0 128

Now we have an address, so clearly the next step is to dump the object:

0:038> !do 0c862790

Name: System.Windows.Controls.Button

MethodTable: 508ad1c0

EEClass: 504f3fd4

Size: 128(0x80) bytes

File: C:\Program Files (x86)\Microsoft Silverlight\5.0.61118.0\System.Windows.dll

Fields:

      MT Field Offset Type VT Attr Value Name

50898494 4000499 4 ...eObjectSafeHandle 0 instance 0c86281c m_nativePtr

508986f4 400049a 8 ... System.Windows]] 0 instance 0c862ad4 _valueTable

50e36b40 400049b 24 System.Boolean 1 instance 0 _propagateInheritanceChanged

50894fa4 400049c c ....DependencyObject 0 instance 00000000 _inheritanceParent

508989fc 400049d 10 ...reTypeEventHelper 0 instance 0c862810 _coreTypeEventHelper

508777b8 400049e 14 ...angedEventHandler 0 instance 00000000 DPChanged

50e139ec 400049f 18 System.EventHandler 0 instance 00000000 _inheritaneContextChanged

50875ab4 40004a1 1c ...bject, mscorlib]] 0 instance 0c8628a8 _treeChildren

508735b0 40004a2 20 ...rnal.IManagedPeer 0 instance 0c836118 _treeParent

50e42e50 40004a0 10 ...flection.Assembly 0 shared static _executingAssembly

… 

50e36b40 4001120 64 System.Boolean 1 instance 1 _isLoaded

50e36b40 4001121 65 System.Boolean 1 instance 0 _isMouseCaptured

50e36b40 4001122 66 System.Boolean 1 instance 0 _isSpaceOrEnterKeyDown

50e36b40 4001123 67 System.Boolean 1 instance 0 _isMouseLeftButtonDown

508aa2a4 4001124 6c System.Windows.Point 1 instance 0c8627fc _mousePosition

5089acf4 4001125 58 ...mation.Storyboard 0 instance 00000000 _currentState

50e36b40 4001126 68 System.Boolean 1 instance 0 _suspendStateChanges

50e36b40 4001127 69 System.Boolean 1 instance 0 _isSuspendingIsEnabled

5089eb88 4001128 5c ...tArgs, mscorlib]] 0 instance 00000000 _weakCanExecuteChangedListener

50873a68 4001129 60 ...outedEventHandler 0 instance 0c862fb0 Click

508987dc 400111a e80 ...ependencyProperty 0 shared static ClickModeProperty

    >> Domain:Value 0b0b0cd0:NotInit 0b0807c0:0c84dc6c <<

508987dc 400111b e84 ...ependencyProperty 0 shared static IsFocusedProperty

    >> Domain:Value 0b0b0cd0:NotInit 0b0807c0:0c84dd4c <<

508987dc 400111c e88 ...ependencyProperty 0 shared static IsMouseOverProperty

    >> Domain:Value 0b0b0cd0:NotInit 0b0807c0:0c84dd88 <<

508987dc 400111d e8c ...ependencyProperty 0 shared static IsPressedProperty

    >> Domain:Value 0b0b0cd0:NotInit 0b0807c0:0c84ddf4 <<

508987dc 400111e e90 ...ependencyProperty 0 shared static CommandProperty

    >> Domain:Value 0b0b0cd0:NotInit 0b0807c0:0c84df18 <<

508987dc 400111f e94 ...ependencyProperty 0 shared static CommandParameterProperty

    >> Domain:Value 0b0b0cd0:NotInit 0b0807c0:0c84df78 <<

5088fe30 400112a e98 ...tArgs, mscorlib]] 0 shared static CS$<>9__CachedAnonymousMethodDelegate5

    >> Domain:Value 0b0b0cd0:NotInit 0b0807c0:00000000 <<

 

Some of the above output was trimmed for brevity. We have some more information. We might not have the internals of the System.Windows.Controls.Button memorized, but we do know enough to know that there is very likely a click event. We can guess that event will have the word click in it. Looking in the output above, we find this line:

50873a68 4001129 60 ...outedEventHandler 0 instance 0c862fb0 Click

 

Dumping this object, we find:

0:038> !do 0c862fb0

Name: System.Windows.RoutedEventHandler

MethodTable: 50873a68

EEClass: 504ddb60

Size: 32(0x20) bytes

File: C:\Program Files (x86)\Microsoft Silverlight\5.0.61118.0\System.Windows.dll

Fields:

      MT Field Offset Type VT Attr Value Name

50e36684 400002d 4 System.Object 0 instance 0c8163e8 _target

50e36684 400002e 8 System.Object 0 instance 00000000 _methodBase

50e3dfd8 400002f c System.IntPtr 1 instance 803c430 _methodPtr

50e3dfd8 4000030 10 System.IntPtr 1 instance 0 _methodPtrAux

50e36684 4000031 14 System.Object 0 instance 00000000 _invocationList

50e3dfd8 4000032 18 System.IntPtr 1 instance 0 _invocationCount

 

Again, we have an object whose internals may not be familiar to us, so we have to guess. _target and _methodPtr both seem like good guesses. We’ll start by dumping _target:

0:038> !do 0c8163e8

Name: GuessTheNumber.MainPage

MethodTable: 080356b8

EEClass: 0835374c

Size: 120(0x78) bytes

File: GuessTheNumber, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null

Fields:

      MT Field Offset Type VT Attr Value Name

50898494 4000499 4 ...eObjectSafeHandle 0 instance 0c81646c m_nativePtr

508986f4 400049a 8 ... System.Windows]] 0 instance 00000000 _valueTable

50e36b40 400049b 24 System.Boolean 1 instance 0 _propagateInheritanceChanged

50894fa4 400049c c ....DependencyObject 0 instance 00000000 _inheritanceParent

508989fc 400049d 10 ...reTypeEventHelper 0 instance 0c816460 _coreTypeEventHelper

508777b8 400049e 14 ...angedEventHandler 0 instance 00000000 DPChanged

50e139ec 400049f 18 System.EventHandler 0 instance 00000000 _inheritaneContextChanged

50875ab4 40004a1 1c ...bject, mscorlib]] 0 instance 0c8637c8 _treeChildren

508735b0 40004a2 20 ...rnal.IManagedPeer 0 instance 00000000 _treeParent

50e42e50 40004a0 10 ...flection.Assembly 0 shared static _executingAssembly

08035848 4000002 54 ...er.MyRandomObject 0 instance 0c8164a8 randomObject

5089f2f4 4000003 58 ...ows.Controls.Grid 0 instance 0c836118 LayoutRoot

08035b18 4000004 5c ...ws.Controls.Label 0 instance 0c81dd78 label1

08035b18 4000005 60 ...ws.Controls.Label 0 instance 0c836730 label2

50897f28 4000006 64 ....Controls.TextBox 0 instance 0c83779c guessTextBox

508ad1c0 4000007 68 ...s.Controls.Button 0 instance 0c862790 submitButton

08035b18 4000008 6c ...ws.Controls.Label 0 instance 0c863124 resultLabel

50e36b40 4000009 70 System.Boolean 1 instance 1 _contentLoaded

 

The abbreviated content above looks promising. The controls listed as members of the object look promising. We still don’t have a click event though. We’re left guessing one more time. This time, we’ll guess that the click method is associated with the MethodTable for GuessTheNumber.MainPage. Based on this guess, we’ll dump the method descriptors for the MethodTable:

0:038> !dumpmt -md 080356b8

EEClass: 0835374c

Module: 080346e4

Name: GuessTheNumber.MainPage

mdToken: 02000003

File: GuessTheNumber, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null

BaseSize: 0x78

ComponentSize: 0x0

Slots in VTable: 63

Number of IFaces in IFaceMap: 4

--------------------------------------

MethodDesc Table

   Entry MethodDe JIT Name

50c32330 50a1b150 PreJIT System.Object.ToString()

50c32350 50a1b158 PreJIT System.Object.Equals(System.Object)

50c323c0 50a1b178 PreJIT System.Object.GetHashCode()

50c323d0 50a1b190 PreJIT System.Object.Finalize()

5074cd34 50508d44 PreJIT

50785800 50514cbc PreJIT System.Windows.Controls.Control.OnManipulationCompleted(System.Windows.Input.ManipulationCompletedEventArgs)

50785828 50514cc4 PreJIT System.Windows.Controls.Control.OnTap(System.Windows.Input.GestureEventArgs)

50785850 50514ccc PreJIT System.Windows.Controls.Control.OnDoubleTap(System.Windows.Input.GestureEventArgs)

50785878 50514cd4 PreJIT System.Windows.Controls.Control.OnHold(System.Windows.Input.GestureEventArgs)

08370240 0803566c JIT GuessTheNumber.MainPage..ctor()

0803c071 08035674 NONE GuessTheNumber.MainPage.submitButton_Click(System.Object, System.Windows.RoutedEventArgs)

083703d0 08035680 JIT GuessTheNumber.MainPage.InitializeComponent() 

Looks like we’re on to something. The submitButton_Click event looks promising. We can guess that is the method we’ve been looking for and dump out the IL:

0:038> !dumpil 08035674  

ilAddr = 083302c8

IL_0000: nop

IL_0001: ldarg.0

IL_0002: ldfld GuessTheNumber.MainPage::guessTextBox

IL_0007: callvirt System.Windows.Controls.TextBox::get_Text

IL_000c: ldloca.s VAR OR ARG 0

IL_000e: call System.Int32::TryParse

IL_0013: pop

IL_0014: ldloc.0

IL_0015: ldarg.0

IL_0016: ldfld GuessTheNumber.MainPage::randomObject

IL_001b: callvirt GuessTheNumber.MyRandomObject::get_randomValue

IL_0020: ceq

IL_0022: ldc.i4.0

IL_0023: ceq

IL_0025: stloc.1

IL_0026: ldloc.1

IL_0027: brtrue.s IL_003c

IL_0029: ldarg.0

IL_002a: ldfld GuessTheNumber.MainPage::resultLabel

IL_002f: ldstr "You chose wisely."

IL_0034: callvirt System.Windows.Controls.ContentControl::set_Content

IL_0039: nop

IL_003a: br.s IL_0058

IL_003c: ldarg.0

IL_003d: ldfld GuessTheNumber.MainPage::resultLabel

IL_0042: ldstr "Your guess of {0} was incorrect."

IL_0047: ldloc.0

IL_0048: box System.Int32

IL_004d: call System.String::Format

IL_0052: callvirt System.Windows.Controls.ContentControl::set_Content

IL_0057: nop

IL_0058: ret

IL isn’t always easy to follow without practice, but the above makes it clear we are in the right place since we can see both, “You chose wisely,” and “Your guess of {0} was incorrect.” To make the code easier to read, we can extract the module and decompile the IL into C#

0:038> !dumpmodule 080346e4

Name: GuessTheNumber, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null

Attributes: PEFile

Assembly: 0b0c2310

LoaderHeap: 00000000

TypeDefToMethodTableMap: 08030124

TypeRefToMethodTableMap: 0803013c

MethodDefToDescMap: 0803020c

FieldDefToDescMap: 08030248

MemberRefToDescMap: 0803027c

FileReferencesMap: 0803035c

AssemblyReferencesMap: 08030360

MetaData start address: 08330d00 (4512 bytes)

0:038> lmm GuessTheNumber

start end module name

08330000 08332600 GuessTheNumber C (no symbols)          

0:038> !savemodule 08330000 d:\GuessTheNumberModule.dll

3 sections in file

section 0 - VA=2000, VASize=1da4, FileAddr=200, FileSize=1e00

section 1 - VA=4000, VASize=370, FileAddr=2000, FileSize=400

section 2 - VA=6000, VASize=c, FileAddr=2400, FileSize=200

 

Using a decompiler such as Redgate’s reflector, we see the following C#:

private void submitButton_Click(object sender, RoutedEventArgs e)

{

    int num;

    int.TryParse(this.guessTextBox.get_Text(), out num);

    if (num == this.randomObject.randomValue)

    {

        this.resultLabel.set_Content("You chose wisely.");

    }

    else

    {

        this.resultLabel.set_Content(string.Format("Your guess of {0} was incorrect.", num));

    }

}

Now it’s clear what’s going on here. The value entered by the user into the text box is parsed into an int then compared to another int stored in this.randomObject.randomValue. All that’s left for us to do is to find the current value for randomValue. We know that in this case, this refers to a MainPage object. Interestingly, that’s the exact type we dumped previously as the _target from the event handler that we found. Reviewing our previous output, we find:

08035848 4000002 54 ...er.MyRandomObject 0 instance 0c8164a8 randomObject

Dumping the object we see:

0:038> !do 0c8164a8

Name: GuessTheNumber.MyRandomObject

MethodTable: 08035848

EEClass: 08353800

Size: 12(0xc) bytes

File: GuessTheNumber, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null

Fields:

      MT Field Offset Type VT Attr Value Name

50e371d4 4000001 4 System.Int32 1 instance 174 <randomValue>k__BackingField

We see a member variable named randomValue which is exactly what we were looking for. Using the value current value of 174, we’ll try another guess in the application:

 

Good guess.

To recap, we were tasked with troubleshooting an application that we knew very little about. We asked a few questions, used a bit of prior knowledge, and a handful of reason driven guesses to get us to a solution.

Although it’s unlikely you will ever need to cheat at a Guess the Number game in a production environment, the approach here is applicable to any situation in which you are approaching with limited domain specific knowledge.

Guess wisely my friends.

 

-Jarrod

Comments

  • Anonymous
    March 13, 2012
    Why did u just load it up in ilspy? ;)

  • Anonymous
    March 19, 2012
    @Nathan: Because ILSpy is not always an immediate option.  Sometimes we're given just a memory dump to work with and would have to, at the very least, extract the related modules before ILSpy would help us out. It's also not always practical within time constraints to do a code review to identify the root cause of an issue.  In this case, I'm sure any developer could have read through all 60 lines of code in no time flat, but if your production application only has 60 lines of code, you probably won't have many problems with it anyway. As for why ILSpy over Reflector?  No real reason there.  I just picked one. Reflector is great.  ILSpy is great.  JetBrains's dotPeek is great.  Telerik's JustDecompile is great.  They're all great.  I'm seeing a compare and contrast of decompilers article in my future.