Sijin Joseph's blog

My experiences with .Net

February 2004 - Posts

Posts of the Day

Came across some really good reads today

  • Kirk Allen writes about the choices we developers have to make in face of ignorant management. “They are millionaires”
  • Joel Pobar has an excellent post on the implementation of the GC in Rotor.
  • Panoptican discusses about VB background compilation, which actually is quite amazing if you imagine all the things that are involved in it.
Managing AssemblyVersion I

I have written a macro that autoincrements the build number of the AssemblyVersion based on the code that is available at http://blogs.biasecurities.com/jim/archive/2003/10/08/166.aspx

My macro uses ProjectItem to look for the AssemblyInfo file, uses regular expressions and supports automatically checking out and checkingIn the assemblyInfo file. I have tested it for C# projects only but i am sure with a few modifications it would work with VB.Net projects as well.

Imports EnvDTE
Imports System.Diagnostics

Public Module BuildIncrementer

    ' This event will be triggered after every build of a project
    ' You can modify the code below to only update projects that are active
    ' It currently will scan all projects in the solution for AssemblyInfo.cs files
    ' to update.
    Sub IncrementBuildNumber()

        'Comment the follow 3 lines, if you want the build number to increment even if the build fails
        If DTE.Solution.SolutionBuild.LastBuildInfo() <> 0 Then
            Exit Sub
        End If

        Dim skippedProjects As Integer = 0
        Dim revisionNo As String = GenerateRevisionNumber()
        ' Change this, if you would only like to modify the AssemblyInfo file in active project files
        ' For Each proj As Project In DTE.ActiveSolutionProjects
        For Each project As Project In DTE.Solution.Projects

            'Try to get the AssemblyInfo item, if not then skip
            'this project
            Dim assemblyInfoItem As ProjectItem = GetAssemblyInfoItem(project)
            Dim skipProject As Boolean = False
            If assemblyInfoItem Is Nothing Then
                skipProject = True
            End If

            If Not skipProject Then
                Try
                    assemblyInfoItem.Open("{7651A701-06E5-11D1-8EBD-00A0C90F26EA}").Activate() 'vsViewKindCode
                    Dim build As TextRange
                    Dim revision As TextRange

                    Dim txtDoc As TextDocument = assemblyInfoItem.Document.Object("TextDocument")
                    GetBuildAndRevision(txtDoc, build, revision)
                    CheckoutActiveItem()
                    IncrementIfNotEmptyOrStar(build)
                    SetToTimeOfDayIfNotEmptyOrStar(revision, revisionNo)
                    assemblyInfoItem.Save()
                    CheckinActiveItem()
                    DTE.ActiveWindow.Close(vsSaveChanges.vsSaveChangesYes)

                Catch ex As System.Exception
                    MsgBox(ex.Message)
                End Try
            Else
                skippedProjects = skippedProjects + 1
            End If
        Next
        System.Diagnostics.Debug.WriteLine("Skipped " & skippedProjects.ToString() & "projects")
    End Sub

    'Returns the full path of the first file
    'in the project with the name AssemblyInfo
    Function GetAssemblyInfoItem(ByVal project As Project) As ProjectItem

        For Each projectItem As ProjectItem In project.ProjectItems
            Dim i As Integer
            For i = 1 To projectItem.FileCount
                Dim filepath As String
                filepath = projectItem.FileNames(i)

                Dim indexOfLastSep As Integer = 1
                indexOfLastSep = filepath.LastIndexOf("\")
                If indexOfLastSep < 1 Then indexOfLastSep = 1

                Dim fileName As String = filepath.Substring(indexOfLastSep)

                If fileName.IndexOf("AssemblyInfo") > 0 Then
                    Return projectItem
                End If
            Next
        Next

        Return Nothing

    End Function
    Sub GetBuildAndRevision(ByVal textDocument As TextDocument, ByRef build As TextRange, ByRef revision As TextRange)

        Dim txtrgs As TextRanges
        build = Nothing
        revision = Nothing

        'Regular Expressions use VC6 syntax and not the new .Net syntax
        If textDocument.StartPoint.CreateEditPoint().FindPattern("AssemblyVersion\("":d+\.:d+\.{(:d+|\*)}(\.{(:d+|\*)})*""", vsFindOptions.vsFindOptionsRegularExpression, Nothing, txtrgs) Then
            If (txtrgs.Count > 1) Then
                build = txtrgs.Item(2)
            ElseIf txtrgs.Count > 2 Then
                revision = txtrgs.Item(3)
            End If
        End If
    End Sub
   

    'Generates a revision number for this build by using the curent time
    Function GenerateRevisionNumber() As String
        Dim now As Date = DateAndTime.Now
        Dim hrs As String = now.Hour.ToString()
        Dim mins As String = now.Minute.ToString()
        Dim secs As String = now.Second.ToString()

        Return hrs & mins & secs
    End Function
   
    'Retrevies the text from a text range
    Function GetTextFromTextRange(ByVal textRange As TextRange) As String
        If textRange Is Nothing Then
            Return ""
        Else
            Return textRange.StartPoint.GetText(textRange.EndPoint)
        End If
    End Function
   
    'Retrieves the text from the range and if it star or empty then
    'returns otherwise increments it
    Sub IncrementIfNotEmptyOrStar(ByVal build As TextRange)
        'If build is * or empty then ignore
        If (Not build Is Nothing) AndAlso (Not GetTextFromTextRange(build).Equals("*")) Then
            Dim buildNo As Integer = CType(GetTextFromTextRange(build), Integer)
            buildNo = buildNo + 1
            build.StartPoint.ReplaceText(build.EndPoint, buildNo.ToString(), sEPReplaceTextOptions.vsEPReplaceTextAutoformat)
        End If
    End Sub
   
    'Retrieves the text from the range and if it star or empty then
    'returns otherwise sets it to the time of day
    Sub SetToTimeOfDayIfNotEmptyOrStar(ByVal revision As TextRange, ByVal revisionNo As String)
        'If revision is * or empty then ignore
        If (Not revision Is Nothing) AndAlso (Not GetTextFromTextRange(revision).Equals("*")) Then
            revision.StartPoint.ReplaceText(revision.EndPoint, revisionNo, vsEPReplaceTextOptions.vsEPReplaceTextAutoformat)
        End If
    End Sub
   
    Sub CheckoutActiveItem()
        Dim checkoutCmd As Command = DTE.Commands.Item("File.CheckOutDynamicSilent")
        'Check if the item is already checked out or not
        If Not checkoutCmd.IsAvailable Then
            System.Diagnostics.Debug.WriteLine("Checkout cmd not available")
            Return
        End If

        Dim customin, customout
        Try
            DTE.Commands.Raise(checkoutCmd.Guid, checkoutCmd.ID, customin, customout)
        Catch ex As System.Exception
            System.Diagnostics.Debug.WriteLine(ex)
        End Try
    End Sub
  
    Sub CheckinActiveItem()
        Dim checkinCmd As Command = DTE.Commands.Item("File.CheckInDynamicSilent")
        'Check if the item is already checked out or not
        If Not checkinCmd.IsAvailable Then
            System.Diagnostics.Debug.WriteLine("Checkin cmd not available, something is wrong")
            Return
        End If
        Dim customin, customout
        DTE.Commands.Raise(checkinCmd.Guid, checkinCmd.ID, customin, customout)
    End Sub
   

Also if you want this macro to be automatically executed when the build finishes add this code to the EnvironmentEvents module that gets automatically generated whenever you create a new Macro project

Private Sub BuildEvents_OnBuildDone(ByVal Scope As EnvDTE.vsBuildScope, ByVal Action As EnvDTE.vsBuildAction) Handles BuildEvents.OnBuildDone
        IncrementBuildNumber()
End Sub

Posted: Feb 19 2004, 06:40 PM by Sijin Joseph | with 6 comment(s)
Filed under:
CVS .Net

It's been 2 weeks since i switched my SCC provider to CVSNT http://www.cvsnt.org . It has solved a lot of problems that i had before when i wanted to work on a new release but still maintain the old codeline for bug fixes and patches. Now using the branching capabilities of CVS i have two codelines life is much easier.

Also now that i have the hang of both WinCVS http://www.wincvs.org and TortoiseCVS http://www.tortoisecvs.org

it's great. The CVS-SCC Proxy from PushOk http://www.pushok.com is working great, i have had no problems till now. It amazes me that people still use VSS for large scale team development, i can't imagine how people manage branches, merging etc. Is it just me or is there something amiss.

Posted: Feb 13 2004, 03:20 PM by Sijin Joseph | with 6 comment(s)
Filed under:
Decrypting IL strings and obfuscation in general

Some obfuscators encrypt the strings used in .Net assemblies. They typically change a line like

ldstr “Hello World

to

ldstr bytearray ( 00 4E........F6) //Not accurate representation

It is easy to detect such encrypted strings in the assemblies because you will see code like

ldstr bytearray (xx xx xx .... xx)

//Followed by

call decrypt //ofcourse the name wouldn't be so obvious, after this call the decrypted string is available on the stack

(As a side note i would like to mention that IL is stack based arguments are taken from stack and return values are put back into the stack)

Now as i was going through one such assembly i noticed that they were using a decrytping method that was global, i.e. there was no type or namespace associated with it, it was just like a global function. Now in my two years of working with .Net i never realized that such a thing could be done, i had thought that the closest to a global function you can get in .Net is static public method in a class. But here was a method that had no namespace and no class in ILDASM it looks like

.method privatescope hidebysig static string decrypt(string A_0) cil managed

there is no enclosing class declaration.

Well i guess this is some more IL magic that is hidden from us C# users :)

Anyways a quick google shows this excrept from DNJ Online

“The .NET Framework does allow you to have global methods, but of all the .NET languages only Managed C++ allows you to declare global methods. The other languages only allow methods to be declared as part of a class. You access a global method from outside of the assembly where it is defined by using an instance of the Module class for the module in which it is defined.“

Very intresting indeed, i never knew that! Pretty Smart.

Now in the unmanaged world if you found such code you would normally try to determine the alogorithm used to decrypt the string. That can be done in this case too, but let's use the language interoperability feature of .Net to our use.

So we create a dummy DLL which has just one public function that takes a string and returns a string

class Decrypter

{

public static string Decrypt(string input)

{

return null;

}

}

Compile it into a DLL, then open it using ILDASM

Now copy the IL code from the orignal Decrypt into yours save the IL file and compile using ILASM,

you've got urself an assembly with a method that can decrypt the strings in the orignal assembly.

Now that i think of it, an easier method would be to just use Reflection to get the Methods of the Module and then use that.

If you want to see something visual make a winforms project with two text boxes, one to take the byte array and the other to display the decoded string. Here is the code you'll need to run to decrypt the string,

string encoded = txtEncoded.Text;

string[] strBytes = System.Text.RegularExpressions.Regex.Split(encoded.Trim(),@"\s+");

byte[] bytes = new byte[strBytes.Length];

for(int i = 0; i < strBytes.Length; i++)

{

bytes[i] = Convert.ToByte(strBytes[i],16);

}

char[] chars = new char[bytes.Length/2]; //Unicode strings 2bytes per char

System.Text.Encoding.Unicode.GetChars(bytes,0,bytes.Length,chars,0);

encoded = new string(chars);

//This is the method from the IL assembly we just created previously, you'll need to add a reference to the assembly

txtDecoded.Text = StringDecoder.Decode(encoded);

 

Well this is cool so now just paste the byte array from ILDASM into a textBox click a button and you've got the decrypted string. Great.

Now suppose we want to decrypt the entire assembly then what, well haven't really looked into it yet, but looks like this could help http://research.microsoft.com/projects/ilx/absil.aspx 

It is an abstract reader for IL binaries. This can be used to comb the binary looking for the patterns i mentioned above and then feed all byte arrays through the decoder and write the decrypted strings back to the IL code.

Well this post is now becoming an article so i guess i'll just stop here. There a lot of other intresting things that obfuscators do to confuse Decompilers, i'll talk about that some other day.

 


Posted: Feb 13 2004, 03:09 PM by Sijin Joseph | with 3 comment(s)
Filed under:
Good free components

Found some great components to integrate into my project.

1.      Error Dialog – I was always looking for a better way to show exceptions, one that could help the developer with debugging as well. This component seems to do the job well. http://www.jasonbock.net/errordialog.html

2. DataGrid - Also there is the ExtendedDataGrid which adds a lot of custom DataGridCoulmns into the standard DataGrid. http://dotnet.leadit.be/extendeddatagrid/

 

Posted: Feb 13 2004, 02:22 PM by Sijin Joseph | with 10 comment(s)
Filed under:
More Posts