Case of the Object Is Not Set To An Instance of an Object–.NET Patching

Continuing our series on patching .NET code without source ( https://chentiangemalc.wordpress.com/2015/07/31/case-of-the-black-background-window-net-patching/ )

A .NET application which worked fine on Windows 7, started throwing an exception when opening an image in Windows 10’s default editor. However the images still opened OK. Changing the default image viewer to a 3rd party program also fixed the issue.

Object reference not set to an instance of an object

image

This exception means a method is trying to be called on a null object.

To investigate I asked an engineer to take a dmp file while the error message was displayed with ProcDump http://live.sysinternals.com/procdump.exe

ProcDump -ma <process name>

Because it was a 32-bit .NET application I loaded the dmp in WinDbg x86

0:000> .load C:\windows\microsoft.net\Framework\v2.0.50727\SOS.dll
0:000> .cordll -ve -u -l
CLRDLL: C:\Windows\Microsoft.NET\Framework\v2.0.50727\mscordacwks.dll:2.0.50727.8662 f:0
doesn’t match desired version 2.0.50727.8000 f:0
CLRDLL: Unable to find ” on the path
Cannot Automatically load SOS
CLRDLL: Loaded DLL c:\localsymbols\mscordacwks_x86_x86_2.0.50727.8000.dll\526720FC5b0000\mscordacwks_x86_x86_2.0.50727.8000.dll
CLR DLL status: Loaded DLL c:\localsymbols\mscordacwks_x86_x86_2.0.50727.8000.dll\526720FC5b0000\mscordacwks_x86_x86_2.0.50727.8000.dll
0:000> !pe
Exception object: 030ca204
Exception type: System.NullReferenceException
Message: Object reference not set to an instance of an object.
InnerException: <none>
StackTrace (generated):
    SP       IP       Function
    00EAE610 0A9E39F9 wds!StreamlineUI.MainForm.DisplayImage(System.String)+0x91

StackTraceString: <none>
HResult: 80004003
0:000> !clrstack
OS Thread Id: 0xfe4 (0)
ESP       EIP    
00ead69c 74b5bfbc [NDirectMethodFrameStandalone: 00ead69c] System.Windows.Forms.SafeNativeMethods.MessageBox(System.Runtime.InteropServices.HandleRef, System.String, System.String, Int32)
00ead6b8 65a61dc8 System.Windows.Forms.MessageBox.ShowCore(System.Windows.Forms.IWin32Window, System.String, System.String, System.Windows.Forms.MessageBoxButtons, System.Windows.Forms.MessageBoxIcon, System.Windows.Forms.MessageBoxDefaultButton, System.Windows.Forms.MessageBoxOptions, Boolean)
00ead758 65a619de System.Windows.Forms.MessageBox.Show(System.String)
00ead760 0a9e3a68 StreamlineUI.MainForm.DisplayImage(System.String)
00eae640 0a19b912 StreamlineUI.MainForm.doDataBind()
00eae668 0a19a542 StreamlineUI.MainForm.getNextForm(ACTION, DIRECTION, System.String)
00eae690 0a199fae StreamlineUI.MainForm.CheckIn(DIRECTION, System.String)
00eae6a8 0a199cfe StreamlineUI.MainForm.btnNext_Click(System.Object, System.EventArgs)
00eae6e0 65435170 System.Windows.Forms.Control.OnClick(System.EventArgs)
00eae6f8 6543055a System.Windows.Forms.Button.OnClick(System.EventArgs)
00eae708 65ccac9a System.Windows.Forms.Button.WndProc(System.Windows.Forms.Message ByRef)
00eae714 65469a00 System.Windows.Forms.Control+ControlNativeWindow.OnMessage(System.Windows.Forms.Message ByRef)
00eae71c 65469981 System.Windows.Forms.Control+ControlNativeWindow.WndProc(System.Windows.Forms.Message ByRef)
00eae730 6546985a System.Windows.Forms.NativeWindow.Callback(IntPtr, Int32, IntPtr, IntPtr)
00eae930 011207bc [NDirectMethodFrameStandalone: 00eae930] System.Windows.Forms.UnsafeNativeMethods.SendMessage(System.Runtime.InteropServices.HandleRef, Int32, IntPtr, IntPtr)
00eae94c 65474c34 System.Windows.Forms.Control.SendMessage(Int32, IntPtr, IntPtr)
00eae968 6547453b System.Windows.Forms.Control.ReflectMessageInternal(IntPtr, System.Windows.Forms.Message ByRef)
00eae978 6546497e System.Windows.Forms.Control.WmCommand(System.Windows.Forms.Message ByRef)
00eae988 65469d9c System.Windows.Forms.Control.WndProc(System.Windows.Forms.Message ByRef)
00eae98c 65464fe5 [InlinedCallFrame: 00eae98c]
00eae9ec 65469a00 System.Windows.Forms.Control+ControlNativeWindow.OnMessage(System.Windows.Forms.Message ByRef)
00eae9f4 65469981 System.Windows.Forms.Control+ControlNativeWindow.WndProc(System.Windows.Forms.Message ByRef)
00eaea08 6546985a System.Windows.Forms.NativeWindow.Callback(IntPtr, Int32, IntPtr, IntPtr)
00eaedd8 011207bc [InlinedCallFrame: 00eaedd8] System.Windows.Forms.UnsafeNativeMethods.CallWindowProc(IntPtr, IntPtr, Int32, IntPtr, IntPtr)
00eaedd4 6546a033 System.Windows.Forms.NativeWindow.DefWndProc(System.Windows.Forms.Message ByRef)
00eaee18 65469f8c System.Windows.Forms.Control.DefWndProc(System.Windows.Forms.Message ByRef)
00eaee1c 659994e9 System.Windows.Forms.Control.WmMouseUp(System.Windows.Forms.Message ByRef, System.Windows.Forms.MouseButtons, Int32)
00eaeea8 65ccc3a6 System.Windows.Forms.Control.WndProc(System.Windows.Forms.Message ByRef)
00eaeeac 654638ac [InlinedCallFrame: 00eaeeac]
00eaef44 654637f0 System.Windows.Forms.Button.WndProc(System.Windows.Forms.Message ByRef)
00eaef50 65469a00 System.Windows.Forms.Control+ControlNativeWindow.OnMessage(System.Windows.Forms.Message ByRef)
00eaef58 65469981 System.Windows.Forms.Control+ControlNativeWindow.WndProc(System.Windows.Forms.Message ByRef)
00eaef6c 6546985a System.Windows.Forms.NativeWindow.Callback(IntPtr, Int32, IntPtr, IntPtr)
00eaf134 011207bc [NDirectMethodFrameStandalone: 00eaf134] System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG ByRef)
00eaf144 65479f8e System.Windows.Forms.Application+ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(Int32, Int32, Int32)
00eaf1e0 65479bf7 System.Windows.Forms.Application+ThreadContext.RunMessageLoopInner(Int32, System.Windows.Forms.ApplicationContext)
00eaf234 65479a41 System.Windows.Forms.Application+ThreadContext.RunMessageLoop(Int32, System.Windows.Forms.ApplicationContext)
00eaf264 65436911 System.Windows.Forms.Application.Run(System.Windows.Forms.Form)
00eaf278 050100ab StreamlineUI.Startup.Main()
00eaf4c8 67101b5c [GCFrame: 00eaf4c8]

CLRStack however doesn’t give us a really good indication of where our exception occurred, for this we will get the more verbose stack output:

0:000> !dumpstack

long long stack trace, then finally …

00eae610 0a9e39f8 (MethodDesc 0x10d610c +0x90 StreamlineUI.MainForm.DisplayImage(System.String)) ====> Exception Code c0000005 cxr@eae1ac exr@eae15c
00eae510 75587890 shell32!CShellExecute::Release+0x30, calling shell32!operator delete
00eae518 76e02c1b KERNELBASE!LocalFree+0x27, calling ntdll!RtlFreeHeap
00eae524 76e02c34 KERNELBASE!LocalFree+0x40, calling KERNELBASE!_SEH_epilog4
00eae55c 76e02c34 KERNELBASE!LocalFree+0x40, calling KERNELBASE!_SEH_epilog4
00eae560 010cb550 010cb550, calling KERNELBASE!GetLastError
00eae57c 6683419e (MethodDesc 0x6664a350 +0x2e System.Runtime.InteropServices.Marshal.FreeHGlobal(IntPtr)), calling 66782c9c
00eae59c 6683419e (MethodDesc 0x6664a350 +0x2e System.Runtime.InteropServices.Marshal.FreeHGlobal(IntPtr)), calling 66782c9c
00eae5ac 663db45d (MethodDesc 0x65e68bd0 +0x291 System.Diagnostics.Process.StartWithShellExecuteEx(System.Diagnostics.ProcessStartInfo)), calling (MethodDesc 0x6664a350 +0 System.Runtime.InteropServices.Marshal.FreeHGlobal(IntPtr))
00eae5e0 663da5cd (MethodDesc 0x65e68b8c +0x39 System.Diagnostics.Process.Start()), calling (MethodDesc 0x65e68bd0 +0 System.Diagnostics.Process.StartWithShellExecuteEx(System.Diagnostics.ProcessStartInfo))
00eae5f0 663da29a (MethodDesc 0x65e68c0c +0x32 System.Diagnostics.Process.Start(System.Diagnostics.ProcessStartInfo)), calling 65f49dc0
00eae600 663da30c (MethodDesc 0x65e68bf4 +0x20 System.Diagnostics.Process.Start(System.String)), calling (MethodDesc 0x65e68c0c +0 System.Diagnostics.Process.Start(System.Diagnostics.ProcessStartInfo))
00eae608 0a9e39f2 (MethodDesc 0x10d610c +0x8a StreamlineUI.MainForm.DisplayImage(System.String)), calling mscorwks!JIT_WriteBarrierEAX
00eae638 0a19b912 (MethodDesc 0x10d6118 +0x262 StreamlineUI.MainForm.doDataBind()), calling 0570a8b8

even more long stack trace continues…

From this we find Process.Start is the last code called before the MessageBox showing the error. Now let’s decompile the function that threw the exception:

0:000> !name2ee wds!StreamlineUI.MainForm.DisplayImage
Module: 010d2c5c (WDS.exe)
Token: 0x060000d3
MethodDesc: 010d610c
Name: StreamlineUI.MainForm.DisplayImage(System.String)
JITTED Code Address: 0a9e3968
0:000> !dumpil 010d610c
ilAddr = 00ceef7c
.try
{
  IL_0000: ldarg.1
  IL_0001: call System.IO.File::Exists
  IL_0006: brtrue.s IL_003d
  IL_0008: ldstr “Unable to locate image file.”
  IL_000d: call System.Environment::get_NewLine
  IL_0012: call System.Environment::get_NewLine
  IL_0017: ldarg.1
  IL_0018: call System.String::Concat
  IL_001d: ldstr “Info”
  IL_0022: call System.Windows.Forms.MessageBox::Show
  IL_0027: pop
  IL_0028: ldarg.0
  IL_0029: ldc.i4.1
  IL_002a: stfld StreamlineUI.MainForm::IMAGEMISSING
  IL_002f: ldarg.0
  IL_0030: ldc.i4.6
  IL_0031: call StreamlineUI.MainForm::set_APPMODE
  IL_0036: ldc.i4.0
  IL_0037: stloc.1
  IL_0038: leave IL_00c2
  IL_003d: ldarg.0
  IL_003e: call StreamlineUI.MainForm::KillImageProcess
  IL_0043: pop
  IL_0044: ldarg.0
  IL_0045: ldarg.1
  IL_0046: call System.Diagnostics.Process::Start
  IL_004b: stfld StreamlineUI.MainForm::imageViewProcess
  IL_0050: ldarg.0
  IL_0051: ldfld StreamlineUI.MainForm::imageViewProcess

We can infer the call to WaitForInputIdle this is the culprit. It is calling a method on the return value from Process Start

  IL_0056: callvirt System.Diagnostics.Process::WaitForInputIdle
  

  IL_005b: pop
  IL_005c: ldarg.0
  IL_005d: call StreamlineUI.MainForm::get_APPMODE
  IL_0062: ldc.i4.2
  IL_0063: bne.un.s IL_0093
  IL_0065: ldarg.0
  IL_0066: ldfld StreamlineUI.MainForm::grdTransactions
  IL_006b: callvirt Infragistics.Win.UltraWinGrid.UltraGridBase::get_Rows
  IL_0070: callvirt Infragistics.Shared.DisposableObjectCollectionBas::get_Count
  IL_0075: brtrue.s IL_0093
  IL_0077: ldarg.0
  IL_0078: ldfld StreamlineUI.MainForm::grdTransactions
  IL_007d: callvirt Infragistics.Win.UltraWinGrid.UltraGridBase::get_DisplayLayout
  IL_0082: callvirt Infragistics.Win.UltraWinGrid.UltraGridLayout::get_Bands
  IL_0087: ldc.i4.0
  IL_0088: callvirt Infragistics.Win.UltraWinGrid.BandsCollection::get_Item
  IL_008d: callvirt Infragistics.Win.UltraWinGrid.UltraGridBand::AddNew
  IL_0092: pop
  IL_0093: ldarg.0
  IL_0094: call StreamlineUI.MainForm::EnterEditMode
  IL_0099: ldarg.0
  IL_009a: ldc.i4.0
  IL_009b: stfld StreamlineUI.MainForm::IMAGEMISSING
  IL_00a0: leave.s IL_00ba
} // end .try
.catch
{
  IL_00a2: stloc.0
  IL_00a3: ldloc.0
  IL_00a4: callvirt System.Exception::get_Message
  IL_00a9: call System.Windows.Forms.MessageBox::Show <- displays the error message
  IL_00ae: pop
  IL_00af: ldarg.0
  IL_00b0: ldc.i4.1
  IL_00b1: stfld StreamlineUI.MainForm::IMAGEMISSING
  IL_00b6: ldc.i4.0
  IL_00b7: stloc.1
  IL_00b8: leave.s IL_00c2
} // end .catch
IL_00ba: ldarg.0
IL_00bb: call System.Windows.Forms.Form::Activate
IL_00c0: ldc.i4.1
IL_00c1: ret
IL_00c2: ldloc.1
IL_00c3: ret

If you are uncomfortable with IL we can save the module then open with .NET Reflector:

0:000> lmvm wds
Browse full module list
start    end        module name
00ce0000 00d26000   wds      C (no symbols)          
    Loaded symbol image file: wds.exe
    Image path: C:\Users\user\WDSv5.4\UAT\wds.exe
    Image name: wds.exe
    Browse all global symbols  functions  data
    Has CLR image header, track-debug-data flag not set
    Timestamp:        Thu May 08 14:10:04 2014 (536B039C)
    CheckSum:         00000000
    ImageSize:        00046000
    File version:     5.0.0.4
    Product version:  5.0.0.4
    File flags:       0 (Mask 3F)
    File OS:          4 Unknown Win32
    File type:        1.0 App
    File date:        00000000.00000000
    Translations:     0000.04b0
    CompanyName:      IT Dept
    ProductName:      Work Distribution System
    InternalName:     WDS.exe
    OriginalFilename: WDS.exe
    ProductVersion:   5.0.0.4
    FileVersion:      5.0.0.4
    FileDescription:  WDS Application
    LegalCopyright:   2005, IT Dept
0:000> !savemodule 00ce0000 c:\support\wds.exe
3 sections in file
section 0 – VA=2000, VASize=3f064, FileAddr=1000, FileSize=40000
section 1 – VA=42000, VASize=730, FileAddr=41000, FileSize=1000
section 2 – VA=44000, VASize=c, FileAddr=42000, FileSize=1000

The section of code at fault decompiled in C#

We expect this.imageViewProcess must be NULL when this.imageViewProcess.WaitForInputIdle() is called

private bool DisplayImage(string imagePath) { try { if (!File.Exists(imagePath)) { MessageBox.Show("Unable to locate image file." + Environment.NewLine + Environment.NewLine + imagePath, "Info"); this.IMAGEMISSING = true; this.APPMODE = 6; return false; } this.KillImageProcess(); this.imageViewProcess = Process.Start(imagePath); this.imageViewProcess.WaitForInputIdle(); if ((this.APPMODE == 2) && (this.grdTransactions.get_Rows().get_Count() == 0)) { this.grdTransactions.get_DisplayLayout().get_Bands().get_Item(0).AddNew(); } this.EnterEditMode(); this.IMAGEMISSING = false; } catch (Exception exception) { MessageBox.Show(exception.Message); this.IMAGEMISSING = true; return false; } base.Activate(); return true; }

Let’s test the theory in PowerShell, you can see when attempting to Process Start an image then call WaitForInputIdle we get an InvokeMethodOnNull exception, this is exactly the same exception thrown by our .NET application.

PS C:\Users\Malcolm> $p = [System.Diagnostics.Process]::Start("notepad.exe") $p.WaitForInputIdle() True PS C:\Users\Malcolm> $p = [System.Diagnostics.Process]::Start("C:\Users\Malcolm\Pictures\DSC01230.JPG") $p.WaitForInputIdle() You cannot call a method on a null-valued expression. At line:3 char:1 + $p.WaitForInputIdle() + ~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : InvalidOperation: (:) [], RuntimeException + FullyQualifiedErrorId : InvokeMethodOnNull PS C:\Users\Malcolm>

Why Process.Start returning NULL?  Checking the .NET documentation  at https://msdn.microsoft.com/en-US/library/53ezey2s(v=vs.80).aspx

Return Value

A new Process component that is associated with the process resource, or a null reference (Nothing in Visual Basic), if no process resource is started (for example, if an existing process is reused).

Use this overload to start a process resource by specifying its file name. The overload associates the resource with a new Process component. If the process is already running, no additional process resource is started. Instead, the existing process resource is reused and no new Process component is created. In such a case, instead of returning a new Process component, Start returns a null reference (Nothing in Visual Basic) to the calling procedure.

Following the source code 

http://referencesource.microsoft.com/#System/services/monitoring/system/diagnosticts/Process.cs

we can see in how the “false” return results in null

public static Process Start(ProcessStartInfo startInfo) { Process process = new Process(); if (startInfo == null) throw new ArgumentNullException("startInfo"); process.StartInfo = startInfo; if (process.Start()) { return process; } return null; // IF process.Start returns FALSE, then we will get NULL back, like in our crashing program }

Following process.Start leads us to StartWithShellExecuteEx where we can see if shellExecuteInfo.hProcess is null, FALSE gets returned.

private bool StartWithShellExecuteEx(ProcessStartInfo startInfo) { <code…> if (shellExecuteInfo.hProcess != (IntPtr)0) { SafeProcessHandle handle = new SafeProcessHandle(shellExecuteInfo.hProcess); SetProcessHandle(handle); return true; }

return false;

 

How do we fix it if the vendor can’t/won’t/source code not available?

PATCH it…

To fix this bug we just need a simple NULL check

To do this we will use ILDASM from Windows SDK to dump the contents into a folder, open the IL file, and search for our DisplayImage method.

Our patched code will be

ldarg.0

ldfld      class [System]System.Diagnostics.Process StreamlineUI.MainForm::imageViewProcess
ldnull
cgt.un
stloc.0
ldloc.0
brfalse.s  IL_005c

Refer to http://www.ecma-international.org/publications/standards/Ecma-335.htm Partition III for an explanation of the commands.

In context:

.method private hidebysig instance bool DisplayImage(string imagePath) cil managed { // Code size 196 (0xc4) .maxstack 4 .locals init (class [mscorlib]System.Exception V_0, bool V_1) .try { IL_0000: ldarg.1 IL_0001: call bool [mscorlib]System.IO.File::Exists(string) IL_0006: brtrue.s IL_003d IL_0008: ldstr "Unable to locate image file." IL_000d: call string [mscorlib]System.Environment::get_NewLine() IL_0012: call string [mscorlib]System.Environment::get_NewLine() IL_0017: ldarg.1 IL_0018: call string [mscorlib]System.String::Concat(string, string, string, string) IL_001d: ldstr "Info" IL_0022: call valuetype [System.Windows.Forms]System.Windows.Forms.DialogResult [System.Windows.Forms]System.Windows.Forms.MessageBox::Show(string, string) IL_0027: pop IL_0028: ldarg.0 IL_0029: ldc.i4.1 IL_002a: stfld bool StreamlineUI.MainForm::IMAGEMISSING IL_002f: ldarg.0 IL_0030: ldc.i4.6 IL_0031: call instance void StreamlineUI.MainForm::set_APPMODE(valuetype [StreamlineClsLib]StreamlineClsLib.Form/ACTION) IL_0036: ldc.i4.0 IL_0037: stloc.1 IL_0038: leave IL_00c2 IL_003d: ldarg.0 IL_003e: call instance bool StreamlineUI.MainForm::KillImageProcess() IL_0043: pop IL_0044: ldarg.0 IL_0045: ldarg.1 IL_0046: call class [System]System.Diagnostics.Process [System]System.Diagnostics.Process::Start(string) IL_004b: stfld class [System]System.Diagnostics.Process StreamlineUI.MainForm::imageViewProcess ldarg.0 ldfld class [System]System.Diagnostics.Process StreamlineUI.MainForm::imageViewProcess ldnull cgt.un stloc.0 ldloc.0 brfalse.s IL_005c IL_0050: ldarg.0 IL_0051: ldfld class [System]System.Diagnostics.Process StreamlineUI.MainForm::imageViewProcess IL_0056: callvirt instance bool [System]System.Diagnostics.Process::WaitForInputIdle() IL_005b: pop IL_005c: ldarg.0 IL_005d: call instance valuetype [StreamlineClsLib]StreamlineClsLib.Form/ACTION StreamlineUI.MainForm::get_APPMODE() IL_0062: ldc.i4.2

We then recompile

c:\windows\microsoft.net\Framework\v2.0.50727\ilasm app.il /RESOURCE=app.res

Check the compiled fix with .NET reflector :

This looks good (!= null decompiles as > null) due to cgt.un instruction which is : Push 1 (of type int32) if value1 > value2, unsigned or unordered, else push 0.

Testing the application, yet another bug patched!

About chentiangemalc

specializes in end-user computing technologies. disclaimer 1) use at your own risk. test any solution in your environment. if you do not understand the impact/consequences of what you're doing please stop, and ask advice from somebody who does. 2) views are my own at the time of posting and do not necessarily represent my current view or the view of my employer and family members/relatives. 3) over the years Microsoft/Citrix/VMWare have given me a few free shirts, pens, paper notebooks/etc. despite these gifts i will try to remain unbiased.
This entry was posted in .NET, C#, Hacking, IL, MSIL, Patching, Reverse Engineering and tagged . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s