Thursday 7 March 2013

Viewing the C# MethodStream for a WebMethod

I was debugging an issue with TeamMentor WebServices and created a view that gave me the MethodStreams for all its source code

MethodStreams are virtual files that contain all relevant 'call-flow source-code' for a particular starting methods (note: MethodStreams are one of O2’s biggest innovation on the SAST world)

This is how a MethodStream file is created
  • Step 1) Add AST for Method A (the first one)
  • Step 2) Add  AST for all methods, fields and properties that Method A used/called
  • Step 3) recursively executed Step 2) until there is nothing else to do
  • Step 4) create the CSharp code for this MethodStream  (based on all the ASTs added)
Here is an example of a Method Stream (from TeamMentor’s SetCurrentPassword Web Services method
using System;
using O2.DotNetWrappers.ExtensionMethods;
using CoreLib;
namespace CoreLib
{
    public class TM_WebServices
    {
        [WebMethod(EnableSession = true)]
        public bool SetCurrentUserPassword(string password)
        {
            return TM_Xml_Database.Current.setCurrentUserPassword(tmAuthentication, password);
        }
        public TM_Authentication tmAuthentication { get; set; }
    }
    public class TM_UserData_Ex_Users
    {
        public static bool setCurrentUserPassword(this TM_Xml_Database tmDb, TM_Authentication tmAuthentication, string password)
        {
            var tmUser = tmAuthentication.currentUser;
            if (tmUser.notNull()) {
                tmUser.PasswordHash = tmUser.createPasswordHash(password);
                tmUser.saveTmUser();
                return true;
            }
            return false;
        }
    }
    public class TMUser_ExtensionMethods
    {
        public static string createPasswordHash(this TMUser tmUser, string password)
        {
            var sha256Hash = tmUser.UserName.hash_SHA256(password);
            var passwordHash = sha256Hash.hash_PBKDF2(tmUser.ID);
            return passwordHash;
        }
    }
    public class SHA256_ExtensionMethods
    {
        public static string hash_SHA256(this string text, string salt)
        {
            var stringToHash = text + salt;
            var sha256 = SHA256.Create();
            var hashBytes = sha256.ComputeHash(stringToHash.asciiBytes());
            var hashString = new StringBuilder();
            foreach (byte b in hashBytes)
                hashString.Append(b.ToString("x2"));
            return hashString.str();
        }
    }
    public class PBKDF2_ExtensionMethods
    {
        public static int DEFAULT_PBKDF2_BYTES = 64;
        public static int DEFAULT_PBKDF2_INTERACTIONS = 20000;
        public static string hash_PBKDF2(this string password, Guid salt)
        {
            return password.hash_PBKDF2(salt.str());
        }
        public static string hash_PBKDF2(this string password, string salt)
        {
            return password.hash_PBKDF2(salt, DEFAULT_PBKDF2_INTERACTIONS, DEFAULT_PBKDF2_BYTES);
        }
        public static string hash_PBKDF2(this string password, string salt, int iterations, int howManyBytes)
        {
            var bytes = PBKDF2.GetBytes(password.asciiBytes(), salt.asciiBytes(), iterations, howManyBytes);
            return bytes.base64Encode();
        }
    }
    public class PBKDF2
    {
        const byte OPAD = 0x5c;
        const byte IPAD = 0x36;
        const int BLOCK_SIZE_IN_BYTES = 64;
        const int HASH_SIZE_IN_BYTES = 32;
        public static byte[] GetBytes(string password, byte[] salt, int iterations, int howManyBytes)
        {
            return GetBytes(Encoding.UTF8.GetBytes(password), salt, iterations, howManyBytes);
        }
        public static byte[] GetBytes(byte[] password, byte[] salt, int iterations, int howManyBytes)
        {
            uint cBlocks = (uint)((howManyBytes + HASH_SIZE_IN_BYTES - 1) / HASH_SIZE_IN_BYTES);
            byte[] saltAndIndex = new byte[salt.Length + 4];
            Array.Copy(salt, 0, saltAndIndex, 0, salt.Length);
            byte[] output = new byte[cBlocks * HASH_SIZE_IN_BYTES];
            int outputOffset = 0;
            SHA256Managed innerHash = new SHA256Managed();
            SHA256Managed outerHash = new SHA256Managed();
            if (password.Length > BLOCK_SIZE_IN_BYTES) {
                password = innerHash.ComputeHash(password);
            }
            byte[] key = new byte[BLOCK_SIZE_IN_BYTES];
            Array.Copy(password, 0, key, 0, password.Length);
            byte[] InnerKey = new byte[BLOCK_SIZE_IN_BYTES];
            byte[] OuterKey = new byte[BLOCK_SIZE_IN_BYTES];
            for (int i = 0; i < BLOCK_SIZE_IN_BYTES; ++i) {
                InnerKey[i] = (byte)(key[i] ^ IPAD);
                OuterKey[i] = (byte)(key[i] ^ OPAD);
            }
            for (int iBlock = 0; iBlock < cBlocks; ++iBlock) {
                _incrementBigEndianIndex(saltAndIndex, salt.Length);
                byte[] U = saltAndIndex;
                for (int i = 0; i < iterations; ++i) {
                    innerHash.Initialize();
                    innerHash.TransformBlock(InnerKey, 0, BLOCK_SIZE_IN_BYTES, InnerKey, 0);
                    innerHash.TransformFinalBlock(U, 0, U.Length);
                    byte[] temp = innerHash.Hash;
                    outerHash.Initialize();
                    outerHash.TransformBlock(OuterKey, 0, BLOCK_SIZE_IN_BYTES, OuterKey, 0);
                    outerHash.TransformFinalBlock(temp, 0, temp.Length);
                    U = outerHash.Hash;
                    _xorByteArray(U, 0, HASH_SIZE_IN_BYTES, output, outputOffset);
                }
                outputOffset += HASH_SIZE_IN_BYTES;
            }
            byte[] result = new byte[howManyBytes];
            Array.Copy(output, 0, result, 0, howManyBytes);
            return result;
        }
        static void _incrementBigEndianIndex(byte[] buf, int offset)
        {
            unchecked {
                if (0 == ++buf[offset + 3])
                    if (0 == ++buf[offset + 2])
                        if (0 == ++buf[offset + 1])
                            if (0 == ++buf[offset + 0])
                                throw new OverflowException();
            }
        }
        static void _xorByteArray(byte[] src, int srcOffset, int cb, byte[] dest, int destOffset)
        {
            int end = checked(srcOffset + cb);
            while (srcOffset != end) {
                dest[destOffset] ^= src[srcOffset];
                ++srcOffset;
                ++destOffset;
            }
        }
    }
    public class TM_UserData_Ex_Users_Persistance
    {
        public static TMUser saveTmUser(this TMUser tmUser)
        {
            if (TM_Xml_Database.Current.UsingFileStorage) {
                lock (tmUser) {
                    tmUser.saveAs(tmUser.getTmUserXmlFile());
                }
            }
            return tmUser;
        }
        public static string getTmUserXmlFile(this TMUser tmUser)
        {
            return TM_UserData.Current.Path_UserData.pathCombine("{0}.userData.xml".format(tmUser.ID));
        }
    }
    public class TM_Authentication
    {
        public TMUser currentUser {
            get {
                try {
                    var tmUser = sessionID.session_TmUser();
                    if (tmUser.notNull())
                        tmUser.CSRF_Token = sessionID.str().hash().str();
                    return tmUser;
                } catch {
                    return new TMUser();
                }
            }
        }
    }
    public class TMUser
    {
        [XmlAttribute()]
        public string PasswordHash { get; set; }
        [XmlAttribute()]
        public string UserName { get; set; }
        [XmlAttribute()]
        public Guid ID { get; set; }
    }
    public class TM_Xml_Database
    {
        public bool UsingFileStorage { get; set; }
        public static TM_Xml_Database Current { get; set; }
    }
    public class TM_UserData
    {
        public string Path_UserData { get; set; }
        public static TM_UserData Current { get; set; }
    }
}
namespace O2.DotNetWrappers.ExtensionMethods
{
    public class Object_ExtensionMethods
    {
        public static bool notNull(this object _object)
        {
            throw new System.Exception("O2 Auto Generated Method");
        }
    }
    public class Web_ExtensionMethods_Encoding
    {
        public static byte[] asciiBytes(this string stringToConvert)
        {
            throw new System.Exception("O2 Auto Generated Method");
        }
        public static string base64Encode(this byte[] bytesToEncode)
        {
            throw new System.Exception("O2 Auto Generated Method");
        }
    }
    public class String_ExtensionMethods
    {
        public static string str(this object _object)
        {
            throw new System.Exception("O2 Auto Generated Method");
        }
    }
}
namespace System
{
    public class Byte : System.ValueType, System.IComparable, System.IFormattable, System.IConvertible, System.IComparable, System.IEquatable
    {
        public string ToString(string format)
        {
            throw new System.Exception("O2 Auto Generated Method");
        }
    }
    public class Int32 : System.ValueType, System.IComparable, System.IFormattable, System.IConvertible, System.IComparable, System.IEquatable
    {
    }
    public class Array : System.ICloneable, System.Collections.IList, System.Collections.ICollection, System.Collections.IEnumerable, System.Collections.IStructuralComparable, System.Collections.IStructuralEquatable
    {
        public int Length {
            get {
                throw new NotImplementedException();
            }
        }
    }
}


Here is the GUI that creates any method stream that I want to see:

image

Here is the script that creates the GUI shown above:
//var topPanel = "{name}".popupWindow(700,400);
var topPanel = panel.clear().add_Panel(); 
var codeViewer = topPanel.insert_Right().add_SourceCodeEditor();
//var textBox = topPanel.add_TextBox(true);
var coreLibFolder = @"{localpath}Web Applications\TeamMentor.CoreLib";
var csharpFiles = coreLibFolder.files("*.cs",true);  
var astData = "astData".o2Cache(()=> { var _astData = new O2MappedAstData();
                                       _astData.loadFiles(csharpFiles);  
                                       
                                       return _astData;});
astData.O2AstResolver.addReference(@"{localpath}\Web Applications\packages\FluentSharp.BCL.5.0.44\lib\net35\FluentSharp.BCL.dll");
astData.O2AstResolver.addReference(@"{localpath}\Web Applications\packages\FluentSharp.CoreLib.5.0.44\lib\net35\FluentSharp.CoreLib.dll");
astData.O2AstResolver.addReference(@"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\System.Web.dll");
astData.O2AstResolver.addReference(@"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\System.Web.Services.dll");

var treeView = topPanel.add_TreeView();
treeView.afterSelect<DefaultMethod>
    ((method)=> {
                    O2Thread.mtaThread(
                        ()=>{
                                "calculating methodStream for: {0}".info(method.fullName());
                                var csharpCode = astData.createO2MethodStream(method).csharpCode();
                                "done".debug();
                                codeViewer.set_Text(csharpCode, ".cs");
                            });
                });
treeView.add_Nodes(astData.iMethods(), (_iMethod)=>_iMethod.Documentation);
treeView.selectFirst();

//using O2.XRules.Database.Languages_and_Frameworks.DotNet
//using O2.API.AST.CSharp  
//using ICSharpCode.SharpDevelop.Dom
//using O2.API.AST.ExtensionMethods.CSharp
//O2File:O2MappedAstData_ExtensionMethods.cs
//O2File:O2MethodStream_ExtensionMethods.cs


Here is simple version (to look at only only file)
//var topPanel = "{name}".popupWindow(700,400);
var topPanel = panel.clear().add_Panel();
var codeEditor = topPanel.add_SourceCodeEditor();
var astData = new O2MappedAstData();

var file = @"{localpath}Web Applications\TeamMentor.CoreLib\TM_AppCode\WebServices_REST\Controllers\TM_REST_Libraries.cs"; 
astData.loadFile(file); 
codeEditor.open(file); 
return astData.iMethods().first().fullName();
//using O2.XRules.Database.Languages_and_Frameworks.DotNet
//using O2.API.AST.CSharp  
//using O2.API.AST.ExtensionMethods.CSharp
//O2File:O2MappedAstData_ExtensionMethods.cs
//O2File:O2MethodStream_ExtensionMethods.cs