Decompile ASP.NET source from deployed dlls

We had an interesting scenario at a client’s a couple of months ago. A staff member had deleted the source code repository for an ASP.NET Web API from Azure DevOps – their justification was that the repository had the name “to be deleted”, so they went ahead and deleted it.

Months later, they realised that actually, the application is still in use in production, and we need the source code back. It could not be restored from Azure DevOps, as it had been deleted too long ago.

An old timer (Mark) had recently left the company and so handed in his laptop, which, fortunately, hadn’t been reformatted, and still had a copy of the source code of the deleted repository on it. So they took the code from his laptop and added it back to Azure DevOps.

But it was always thought that the code recovered from Mark’s laptop wasn’t the latest version of the code, and that what was deployed to production was newer than the recovered code. Therefore the recovered code is read-only and only for reference, and we couldn’t deploy any changes to the API.

About a year goes by, and we need an enhancement done to the production API, which I’m asked to have a look at.

So, I first needed to determine if the source code really was older than what was really deployed. We came up with a plan:

  • deploy the recovered source code for the API to our test server
  • download the compiled dlls from the test Azure app service
  • download the compiled dlls from the production Azure app service
  • decompile the compiled dlls from both to reverse-engineer the source code
  • compare the decompiled source code from both to determine what code changes there are

It could have been quite a laborious process, because the application in question had lots of projects (.csproj files) and thus lots of dlls (28) to compare. But I found a couple of tools which helped greatly.

  • ILSpy (free)
  • Beyond Compare (free trial)

Download the dlls from Azure

You can do this from the Azure Portal using the Kudu console, aka “Advanced tools”, and then the CMD Debug Console:

Decompile source code from the Azure app service website

ILSpy is a great tool for this, and it’s free.

  1. Open ILSpy, and clear out the Assemblies window (Ctrl-A, delete)
  2. Unzip the wwwroot folder you downloaded above
  3. Select the .dlls containing your source code
  4. Drag them into ILSpy’s Assemblies window
  5. Select all of the Assemblies in ILSpy (Ctrl-A)
  6. File -> Save Code. ILSpy will generate a Visual Studio .sln file containing all the code for you – too easy!

I did this for our test server (from the recovered source code), and then again for the code in production.

Compare content of two folders

Once I had my decompiled source from test and production, I needed to compare the 2 to check for differences.

I couldn’t find a decent free tool to easily compare the content of 2 folders. I ended up using Beyond Compare, which I first saw over ten years ago. It has a free trial. I tweaked its options to ignore timestamps.

Leave a comment if you know of a decent freeware tool for comparing folders.

Results

I did find a couple of minor changes between the decompiled code in test and production. I also needed to check other non-code files such as web.config for differences. In the end it looked to me that what was on our test server was actually a  newer version than what was in production. I figured that Mark probably had develop branch with some minor fixes (which never made it to prod) checked out on his laptop when it was recovered, and so what was deployed to prod was older.

I was able to add the requested minor enhancement, but also the team was stoked to know that they now have the source code for their API back. Although, it’s a legacy API which is on the roadmap to be replaced in the next few months anyway…

Advertisement

Conditionally specify a property in an ARM template

At my current contract I’ve been doing a lot more DevOps than I have in the past, mainly because they don’t have enough specialist DevOps engineers. So us developers are encouraged to look after our own Build and Release pipelines and Azure resources. Which means over the past 18 months I’ve spent probably about a month writing ARM (Azure Resource Manager) templates.

Recently I was stuck trying to conditionally specify a property. ARM templates allow you to conditionally specify a resource, via the condition property. But I couldn’t find an easy way to conditionally specify a property. I eventually figured out a way to do it which I couldn’t find documented anywhere.

In my case, I was deploying an API Management ARM template which is shared across our organization, and I wanted to be able to optionally set the OAuth 2.0 server for an API, if the server name is passed in as a parameter. The block in the ARM template looks like this:

{
    "parameters": {
        "oAuthServerId": {
            "type": "string",
            "defaultValue": "",
            "metadata": {
                "description": "The ID of the OAuth 2.0 server."
            }
        }
    },
    "resources": [
        {
            "type": "Microsoft.ApiManagement/service/apis",
            "name": "[variables('apiName')]",
            "apiVersion": "2018-06-01-preview",
            "properties": {
                "authenticationSettings": {
                    "oAuth2": {
                        "authorizationServerId": "[parameters('oAuthServerId')]"
                    }
                }
            }
        }
    ]
}

Because this template is already shared across our organization, and most of the organization’s APIs are not using OAuth (yet), I added a new optional oAuthServerId parameter.
My initial naive implementation was just like the above. My hope was that for all the other APIs that aren’t using OAuth, the empty default value of the parameter would be used (“”), resulting in the following output:

"properties": {
    "authenticationSettings": {
        "oAuth2": {
            "authorizationServerId": ""
        }
    }
}

My hope was that if the authorizationServerId is blank, then Azure would default the User Authorization to “None” (so that I don’t break existing deployments which already use this ARM template):

However, that didn’t work: Authorization server was not found.

I won’t bore you with all the things I tried in the hours that followed as I tried to get this to work. Eventually I found a solution, which is to create a variable for the entire authenticationSettings object and conditionally specify that parent object, rather than trying to conditionally specify the child authentication.oAuth2.authorizationServerId.

{
    "parameters": {
        "oAuthServerId": {
            "type": "string",
            "defaultValue": "",
            "metadata": {
                "description": "The ID of the OAuth 2.0 server."
            }
        }
    },
    "variables": {
        "authenticationSettings": {
            "oAuth2": {
                "authorizationServerId": "[parameters('oAuthServerId')]"
            }
        }
    },
    "resources": [
        {
            "type": "Microsoft.ApiManagement/service/apis",
            "name": "[variables('apiName')]",
            "apiVersion": "2018-06-01-preview",
            "properties": {
                "authenticationSettings": "[if(equals(parameters('oAuthServerId'),''), json('null'), variables('authenticationSettings'))]"
            }
        }
    ]
}

A couple of other tips I’ve found useful for ARM template writing.

  • Always test the ARM templates from Powershell on your local machine, rather than commiting and pushing the changes to Azure DevOps and waiting for DevOps to run your pipeline and eventually fail. As with all development, you’ll be much more efficient if you can shorten your feedback loop.
    1. Install the latest Powershell
    2. Install the latest Azure Powershell
    3. Connect to Azure at the Powershell command line with Connect-AzAccount
    4. Connect to your “Development” subscription with something like Set-AzContext -SubscriptionId f2b1b88a-xxxx-xxxx-a9e1-99a96d8b95f4
    5. Create a parameters file for testing your ARM template locally
    6. Validate your ARM template with Test-AzResourceGroupDeployment
    7. Run your ARM template with New-AzResourceGroupDeployment
  • If you can’t find the name or the property of a resource, check out the (still in Preview after many years) site https://resources.azure.com/