Archives

Archives / 2004 / February
  • 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

  • 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.

  • 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.

     


  • 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/