So here’s the thing – we all hate having to tie our Visual Studio solutions to an IIS web site when the ASP.NET Development Server will do just fine. Sure, it’s best to test our web apps on a live, flesh and blood IIS server – but for small scale projects, why bother? You and I are happy to use it 99% of the time…
… but some times we can’t!
That’s right – ASP.NET Development Server refuses remote connections. What’s an iPhone web app developer to do? Use IIS so I can establish remote connections from my iPhone?
Hell no!
Ladies and gentlemen, I present to you: ILDASM
Ohhhh yeah! It’s time to bring out the big guns. We’re gonna have us a little hackin’ action. If the dev server doesn’t want to play nice, we’ll make it!
Step 1: Let’s grab the dev server executable. You can find out where it’s located by debugging a ASP.NET web project, opening the Task Manager and clicking the “Open File Location” on the WebDev.WebServer.EXE. Copy that file to c:\temp, where we’ll do a little patch work later on. In my case, it’s in the following folder: C:\Program Files (x86)\Common Files\microsoft shared\DevServer\10.0

Step 2: It’s time to perform open heart surgery. Grab Reflector, and let’s get to work. Drag and drop the executable onto Reflector and take a look around. You’ll notice that there’s nothing of real interest here – it delegates most of the heavy lifting to the WebDev.WebHost40 assembly. Let’s figure out where that is like so:


Now that we know where it is, let’s copy that to c:\temp:
xcopy C:\Windows\Microsoft.Net\assembly\GAC_32\WebDev.WebHost40\v4.0_10.0.0.0__b03f5f7f11d50a3a\WebDev.WebHost40.dll c:\temp
Step 3: Back to Reflector! Let’s take a look around for a sec… Did you notice the Request class? Let’s take a look there. Ah! Request.Process() looks rather suspicious:
[AspNetHostingPermission(SecurityAction.Assert, Level=AspNetHostingPermissionLevel.Medium)]
public void Process()
{
if (this.TryParseRequest())
{
if (((this._verb == "POST") && (this._contentLength > 0)) && (this._preloadedContentLength < this._contentLength))
{
this._connection.Write100Continue();
}
if (!this._host.RequireAuthentication || this.TryNtlmAuthenticate())
{
if (this._isClientScriptPath)
{
this._connection.WriteEntireResponseFromFile(this._host.PhysicalClientScriptPath + this._path.Substring(this._host.NormalizedClientScriptPath.Length), false);
}
else if (this.IsRequestForRestrictedDirectory())
{
this._connection.WriteErrorAndClose(0x193);
}
else if (!this.ProcessDefaultDocumentRequest())
{
this.PrepareResponse();
HttpRuntime.ProcessRequest(this);
}
}
}
}
Hmmm… that call to TryParseRequest()… let’s take a look:
private bool TryParseRequest()
{
this.Reset();
this.ReadAllHeaders();
if (!this._connection.IsLocal)
{
this._connection.WriteErrorAndClose(0x193);
return false;
}
if (((this._headerBytes == null) || (this._endHeadersOffset < 0)) || ((this._headerByteStrings == null) || (this._headerByteStrings.Count == 0)))
{
this._connection.WriteErrorAndClose(400);
return false;
}
this.ParseRequestLine();
if (this.IsBadPath())
{
this._connection.WriteErrorAndClose(400);
return false;
}
if (!this._host.IsVirtualPathInApp(this._path, out this._isClientScriptPath))
{
this._connection.WriteErrorAndClose(0x194);
return false;
}
this.ParseHeaders();
this.ParsePostedContent();
return true;
}
Busted! If the connection is not local, return a 403 and close the connection – WriteErrorAndClose(0×193). Let’s put this on our naughty list – we’ll get back to it later on.
Step 4: Hunt down the Loopbacks! Here’s Server.Start():
public void Start()
{
try
{
this._socket = this.CreateSocketBindAndListen(AddressFamily.InterNetwork, IPAddress.Loopback, this._port);
}
catch (Exception exception)
{
SocketException exception2 = exception as SocketException;
if ((exception2 != null) && (exception2.SocketErrorCode == SocketError.AddressAlreadyInUse))
{
throw exception;
}
this._socket = this.CreateSocketBindAndListen(AddressFamily.InterNetworkV6, IPAddress.IPv6Loopback, this._port);
}
if (this._socket != null)
{
ThreadPool.QueueUserWorkItem(this._onStart);
}
}
Those Loopbacks gotta go.
Step 5: It’s surgery time. Use ILDASM to dump WebDev.WebServer.EXE and WebDev.WebHost40.dll to c:\temp\server\WebDev.WebServer.IL and c:\temp\host\WebDev.WebHost40.IL, respectively. When prompted, tick each checkbox on the output options.
Let’s start with the host. Open up WebDev.WebHost40.IL, preferably with a real text editor. Let’s first find those Loopbacks – locate the Server.Start() method:
.method /*0600009B*/ public hidebysig instance void
Start() cil managed
// SIG: 20 00 01
{
Now, replace Loopback and IPV6Loopback with Any and IPV6Any, respectively. In other words, this:
becomes: <pre class="console" IL_0003: /* 7E | (0A)0000A4 */ ldsfld class [System/*23000003*/]System.Net.IPAddress/*01000015*/ [System/*23000003*/]System.Net.IPAddress/*01000015*/::Any /* 0A0000A4 */
Great! Now it’ll accept connections – but it will still respond with a 403 and drop the connection – no bueno! Let’s fix it.
Here’s the offending code:
if (!this._connection.IsLocal)
{
this._connection.WriteErrorAndClose(0x193);
return false;
}
That translates to the following IL (from the TryParseRequest() method):
IL_000c: /* 02 | */ ldarg.0
IL_000d: /* 7B | (04)00004E */ ldfld class Microsoft.VisualStudio.WebHost.Connection/*02000004*/ Microsoft.VisualStudio.WebHost.Request/*0200000B*/::_connection /* 0400004E */
IL_0012: /* 6F | (06)000015 */ callvirt instance bool Microsoft.VisualStudio.WebHost.Connection/*02000004*/::get_IsLocal() /* 06000015 */
IL_0017: /* 2D | 12 */ brtrue.s IL_002b
// this._connection.WriteErrorAndClose(403); return;
IL_0019: /* 02 | */ ldarg.0
IL_001a: /* 7B | (04)00004E */ ldfld class Microsoft.VisualStudio.WebHost.Connection/*02000004*/ Microsoft.VisualStudio.WebHost.Request/*0200000B*/::_connection /* 0400004E */
IL_001f: /* 20 | 93010000 */ ldc.i4 0x193
IL_0024: /* 6F | (06)000023 */ callvirt instance void Microsoft.VisualStudio.WebHost.Connection/*02000004*/::WriteErrorAndClose(int32) /* 06000023 */
IL_0029: /* 16 | */ ldc.i4.0
IL_002a: /* 2A | */ ret
// blah blah blah . . .
Pretty simple, right? If the connection is local, branch – go to IL_002b and continue processing. There’s several ways you can fix this – my suggestion is that you just replace the “this._connection.WriteErrorAndClose(403)” code with NOPs. Here’s what you end up with:
IL_0019: nop
IL_001a: nop
IL_001b: nop
IL_001c: nop
IL_001d: nop
IL_001e: nop
IL_001f: nop
IL_0020: nop
IL_0021: nop
IL_0022: nop
IL_0023: nop
IL_0024: nop
IL_0025: nop
IL_0026: nop
IL_0027: nop
IL_0028: nop
IL_0029: nop
IL_002a: nop
Yay! One last little change:
.assembly /*20000001*/ WebDev.WebHost40
{
// blah blah blah
.ver 10:0:0:0 // Let's bump this up to .ver 10:1:0:0
}
Now, create a strong name key using Sn.exe. After you have your snk, compile the IL using:
ilasm /dll /resource=webdev.webhost40.res /key=c:\temp\mykey.snk webdev.webhost40.il
Install the assembly into the GAC:
gacutil /i c:\temp\webdev.webhost.dll
One assembly down, one more to go; we still need to update the web server’s reference to the new WebDev.WebHost40. Open up WebDev.WebServer40.IL and change this:
.assembly extern /*23000005*/ WebDev.WebHost40
{
.publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A )
.ver 10:0:0:0
}
to
.assembly extern /*23000005*/ WebDev.WebHost40
{
.publickeytoken = (00 00 00 00 00 00 00 00 ) // put _YOUR_ public key here!!!
.ver 10:1:0:0
}
Now it’s time to compile using ilasm:
ilasm /exe /resource=WebDev.WebServer40.res /key=c:\temp\mykey.snk WebDev.WebServer40.il
Give it a test:
c:\temp\server\WebDev.WebServer40.exe /port:1234 /path:c:\path\to\website /vpath:website [/ntlm]
You’re golden, Ponyboy! Create a backup folder in your DevServer\10.0 folder, then feel free to replace the original executable with your patched version.