⭐ If you would like to buy me a coffee, well thank you very much that is mega kind! : https://www.buymeacoffee.com/honeyvig Hire a web Developer and Designer to upgrade and boost your online presence with cutting edge Technologies
Showing posts with label c-sharp. Show all posts
Showing posts with label c-sharp. Show all posts

Sunday, April 24, 2022

C# Predict the Random Number Generator of .NET

 

This post targets to underline the predictability of the random… or better said pseudo-random number generator (PRNG) exposed by the .NET framework (aka the Random() class), under certain assumptions. Because of the nature of the implementation, 100% accuracy can be obtained with a fairly simple idea and a rather short code snippet.

The presented method definitely isn’t something new in the domain of cryptography, however the purpose of the article is to bring awareness about this specific weakness.

The following scenario is considered:

  • no access to the process’s memory
  • must work for any chosen seed
  • a limited set of generated random numbers is visible to the attacker
  • we focus on Random.nextDouble() as there is no data loss because int casting

I’ll be presenting a short summary of the algorithm used by Random() and how can we predict the random numbers. If you feel like going directly to code, scroll down to the bottom of the article.

The Random class

While many pseudo-random implementations (e.g., libc’s rand()) rely on a Linear Congruential Generator (LCG) which generates each number in the sequence by taking into account the previous one, I discovered that .NET’s random number generator uses a different approach.

By looking at the implementation of the Random() class, one can easily observe that pseudo-random number generation is based on a Subtractive Generator, which permits the user to specify a custom seed or use Environment.TickCount (system’s uptime in milliseconds) as default.

The core of the pseudo-random generator is the InternalSample() (line #100) method which constructs the sequence of numbers. Random.nextDouble() will actually call the Sample() method which returns the value of InternalSample() divided by Int32.MaxValue, as this is claimed to improve the distribution of random numbers. Without going into much details regarding the included gimmicks, we can describe the generator as follows:

where

contributes to describing the state of the algorithm and

is, obviously, the returned value.

To store the state of the pseudo-random number generator, a circular array of 56 ints is employed - this means

and

will get re-initialized to 1 whenever they exceed the length of the array - however the offset of 21 remains constant.

Predicting Random Numbers

In my opinion, it seems rather difficult to determine the starting state of the algorithm without knowing the seed. But… we notice that the algorithm is outputting pseudo-random numbers which properly describe each value of its state array.

In other words, if we have access to a randomly generated number

, we can compute and is used to generate future states & numbers in the sequence. However, we will need values for

in order to cover all the properties.

If we manage to leak a continuous set of 55 generated numbers, we have enough information to describe and construct a new generator (by providing a circular array of states) which will output the same numbers as the original but can be used as a predictor.

In my implementation, I’m using the following trick to simplify the things: I don’t convert the leaked

back to (by multiplying with the Int32.MaxValue) because I’ll have to divide it again to compare the results. So I’m working directly with differences of leaked values (instead of differences of

’s) – I hope it makes sense.

Here’s the code I used, it should help clear things up.


public class Program
{
	/* predicts random numbers, given 2 state descriptors */
	public static double computeDiffAndOffset(double r1, double r2)
	{
		double diff = r1 - r2;
		
		if (diff == Int32.MaxValue)
			diff=- 1/(double)Int32.MaxValue;
		if (diff < 0)
			return diff + 1;
		else
			return diff;
	}
	
	public static void Main()
	{
		/* this we break */
		Random r = new Random();
		
		/* describes the state of the subtractive generator */
		double[] SeedArray = new double[56];
		
		/* leaking the state by observing the first 55 random numbers */
		for (int i = 1; i < 56; i++)
			SeedArray[i] = r.NextDouble();
		
		/* the offset is known from the original implementation */
		int offset = 21;
		
		/* from the theory part: i = index1, j = index2 */
		int index1 = 1, index2 = index1 + offset;
		
		/* running a few tests */
		for (int i = 0; i < 1000; i++)
		{
			/* handling the circular array limits */
			if (index1 >= 56)
				index1 = 1;
			
			if (index2 >= 56)
				index2 = 1;
			
			/* this is the predicted random number */
			double predictedValue = computeDiffAndOffset(SeedArray[index1], SeedArray[index2]);

			/* this is the correct random number */
			double correctRandom =  r.NextDouble();
			
			/* we compare them as doubles */
			if (Math.Abs(predictedValue - correctRandom) > 0.00001)
				throw new Exception(String.Format("Failed at {0} vs {1}", predictedValue, correctRandom));
			
			/* printing the results */
			Console.WriteLine("Predicted: " + predictedValue + " | Correct: " + correctRandom);

			/* updating the state of the generator */
			SeedArray[index1] = predictedValue;
			
			index1++;
			index2++;
		}
	}
}

You should get something like this when running it (well, different numbers because you’ll have a different seed - but you get the point). Tested it on .NET 4.7.2.


Predicted: 0.562743733899083 | Correct: 0.562743733899083
Predicted: 0.0782367256834342 | Correct: 0.0782367256834343
Predicted: 0.48149561019684 | Correct: 0.48149561019684
Predicted: 0.768610569075034 | Correct: 0.768610569075034
Predicted: 0.288163338456379 | Correct: 0.288163338456379
Predicted: 0.652038850659523 | Correct: 0.652038850659523
Predicted: 0.331446861071254 | Correct: 0.331446861071255
Predicted: 0.573066327056413 | Correct: 0.573066327056413
[...]

Conclusions

Definitely don’t use Random() for cryptographic functions. Bad idea. However, limiting the information provided to the adversary (i.e. hiding the randomly generated numbers) would greatly diminish the effectiveness of this attack.

Not much else to be said. It’s my first take at breaking something which is not an LCG - it might not be state-of-the-art level (performance-wise) but I hope you found this informative.

RSA: Encrypt in .NET & Decrypt in Python

 

So… one of my current projects required the following actions: asymmetrically encrypt a string in .NET using a public key and decrypt it in a python script using a private key.

The problem that I’ve encountered was that, apparently, I couldn’t achieve compatibility between the two exposed classes: RSACryptoServiceProvider and PKCS1_v1_5. To be more specific, the python script couldn’t decrypt the ciphertext even though proper configurations were made and the provided keys were compatible. Additionally, separate encryption-decryption actions worked inside .NET and python but not in-between them.

I wasn’t able to find too much information about this specific problem in the RSAParameters documentation, hence this post.

Solution

Alright, the issue seems to be caused by a difference in endianness between the two classes, when the RSA parameters are provided. PKCS1_v1_5 uses little endian and RSACryptoServiceProvider prefers big endian. In my case, this made the encryption method use a different key than the one I though I specified. Nevertheless, it was more fun to debug because of PKCS which always ensured different ciphertexts.

I fixed this by base64-encoding the exponent and modulus in big-endian format (in python) and then loading them with RSACryptoServiceProvider.FromXmlString() (in .NET).

Working Example

I hardcoded the (N, E, D) parameters for a private key in python and exported the exponent and modulus to be used later for encryption.


# custom base64 encoding
def b64_enc(n, l):
    n = n.to_bytes(l, 'big')
    return base64.b64encode(n)

# fixed a set of keys for testing purposes
N = 26004126751443262055682011081007404548850063543219588539086190001742195632834884763548378850634989264309169823030784372770378521274048211537270851954737597964394738860810397764157069391719551179298507244962912383723776384386127059976543327113777072990654810746825378287761304202032439750301912045623786736128233730798303406858144431081065384988539277630625160727011582345942687126935423502995613920211095965452425548919926951203151483590222152446516520421379279591807660810550784744188433550335950652666201439521115515355539373928576162221297645781251953236644092963307595988040539993067709240004782161131243282208593
E = 65537
D = 844954574014654722486150458473919587206863455991060222377955072839922571984098861772377020041002939383041291761051853484512886782322743892284027026528735139923685801975918062144627908962369108081178131103781404720078456605432924519279933702927938064507063482999903002331319671303661755165294744970869186178561527578261522199503340027952798084625109041630166309505066404215223685733585467434168146932177924040219720383860880583466676764286302300281603021045351842170755190359364339936360197909582974922675680101321863304283607829144759777189360340512230537108705852116021758740440195445732631657876008160876867027543

# construct pair of keys
private_key = RSA.construct((N, E, D))
public_key = private_key.publickey()

# base64-encode parameters in big-endian format
EXP = b64_enc(public_key.e, 3)
MODULUS = b64_enc(public_key.n, 256)

print('EXP:', EXP, 'MODULUS:', MODULUS)

# Output:
# EXP: b'AQAB' MODULUS: b'zf4LgceVPvjMLz/pp8exH58AeBrhjLe0k4FRmd59I0k4sH6oug6Z9RfY4FvEFcssBwH1cmWF5/Zen8xbRVRyUnzer6b6cKmlzHFYf0LlbovvYMkW5pdhRcTHK2ijByGtmVgU/CEKEQTy3elpU7ZsHE8D6T1M7L2gmGAxvgldUMRu4l8BPuRyht1a9dA9b6005atpdlkCSc3emXSfyBOBwNE0UicVTVncn9SBjP7bTBGgOKshYnYsqh4BD0I7AU3xdoAsZVWudECX/zVa7uUOk1ooVYjMEyfBngrEDXrmIkAlVruUuj/eWiYwT2vXqByQgDfDvat5IS4i3ywiHAWXUQ=='

In .NET (I used C#), there will be something like this:


using System;
using System.Security.Cryptography;
using System.Text;

public class RSACryptoApp
{
    // parameters from the python script (public key)
    private static readonly String EXP = "AQAB";
    private static readonly String MODULUS = "zf4LgceVPvjMLz/pp8exH58AeBrhjLe0k4FRmd59I0k4sH6oug6Z9RfY4FvEFcssBwH1cmWF5/Zen8xbRVRyUnzer6b6cKmlzHFYf0LlbovvYMkW5pdhRcTHK2ijByGtmVgU/CEKEQTy3elpU7ZsHE8D6T1M7L2gmGAxvgldUMRu4l8BPuRyht1a9dA9b6005atpdlkCSc3emXSfyBOBwNE0UicVTVncn9SBjP7bTBGgOKshYnYsqh4BD0I7AU3xdoAsZVWudECX/zVa7uUOk1ooVYjMEyfBngrEDXrmIkAlVruUuj/eWiYwT2vXqByQgDfDvat5IS4i3ywiHAWXUQ==";

    public static void Main(string[] args)
    {
       RSACryptoServiceProvider csp = new RSACryptoServiceProvider(2048);
       csp.FromXmlString("<RSAKeyValue><Exponent>" + EXP + "</Exponent><Modulus>" + MODULUS + "</Modulus></RSAKeyValue>");

       // encrypting a string for testing purposes
       byte[] plainText = Encoding.ASCII.GetBytes("Hello from .NET");
       byte[] cipherText = csp.Encrypt(plainText, false);

       Console.WriteLine("Encrypted: " + Convert.ToBase64String(cipherText));

       // Output:
       // Encrypted: F/agXpfSrs7HSXZz+jVq5no/xyQDXuOiVAG/MOY7WzSlp14vMOTM8TshFiWtegB3+2BZCMOEPLQFFFbxusuCFOYGGJ8yRaV7q985z/UDJVXvbX5ANYqrirobR+c868mY4V33loAt2ZFNXwr+Ubk11my1aJgHmoBem/6yPfoRd9GrZaSQnbJRSa3EDtP+8pXETkF9B98E7KvElrsRTLXEXSBygmeKsyENo5DDcARW+lVVsQuP8wUEGnth9SX4oG8i++gmQKkrv0ep6yFrn05xZJKgpOfRiTTo/Bkh7FxNP2wo7utzhtYkNnvtXaJPWAvqXg93KmNPqg1IsN4P1Swb8w==
    }
}

Back to the python script:


cipher = PKCS1_v1_5.new(private_key)

random_generator = Random.new().read
sentinel = random_generator(20)

cipher_text = 'F/agXpfSrs7HSXZz+jVq5no/xyQDXuOiVAG/MOY7WzSlp14vMOTM8TshFiWtegB3+2BZCMOEPLQFFFbxusuCFOYGGJ8yRaV7q985z/UDJVXvbX5ANYqrirobR+c868mY4V33loAt2ZFNXwr+Ubk11my1aJgHmoBem/6yPfoRd9GrZaSQnbJRSa3EDtP+8pXETkF9B98E7KvElrsRTLXEXSBygmeKsyENo5DDcARW+lVVsQuP8wUEGnth9SX4oG8i++gmQKkrv0ep6yFrn05xZJKgpOfRiTTo/Bkh7FxNP2wo7utzhtYkNnvtXaJPWAvqXg93KmNPqg1IsN4P1Swb8w=='

plain_text = cipher.decrypt(base64.b64decode(cipher_text.encode('ASCII')), sentinel)
print('Decrypted:', plain_text.decode('ASCII'))

# Output:
# Decrypted: Hello from .NET

C# Prevent Decompilation by Decrypting Source at Runtime

 The point is: it is rather difficult to make .NET programs run with a key or license; since these can be reverted back to their sourcecode, anyone can alter it or just learn to create fake keys that will be seen as valid.

 

Possible Solution

One way to make an application a little bit more difficult to crack would be to deliver it as a program that decrypts instructions, compiles and runs them only when needed. This way, if someone finds out where the sourcecode is stored, it will still be encrypted and without a key (or license) it is unusable.

We’re kinda writing polymorphic stuff here - AVs won’t be happy; actually…only 2/57 don’t like it, we’re good.

1. Making the Compiler

We’re not really going to reinvent the wheel here - .NET seems to allow us to use the original compiler to produce an Assembly. Just as always, we start with a CodeDomProvider, add a bunch of settings using CompilerParameters and a few sourcecodes.


CodeDomProvider provider = CodeDomProvider.CreateProvider("CSharp");

CompilerParameters parameters = new CompilerParameters();

parameters.GenerateExecutable = true;
parameters.GenerateInMemory = true; // it's still going to generate a file somewhere in AppData (temp)
parameters.TreatWarningsAsErrors = false;
            
// I need these references because the program that I will 'secure'
// is that Form from the photo above that requires a password
parameters.ReferencedAssemblies.Add("System.Windows.Forms.dll");
parameters.ReferencedAssemblies.Add("System.dll");
parameters.ReferencedAssemblies.Add("System.Drawing.dll");

// getContents() is a method that extracts & decrypts the sourcecodes 
// of the 'secured' application and returns everything as an array of Strings
// in order to be compiled

CompilerResults result = provider.CompileAssemblyFromSource(parameters, getContents());

If you look around, there’s also an article that provides a little bit more detail about how to compile code at runtime using CSharpCodeProvider and ICodeCompiler which are now considered obsolete, but the code is similar.

2. Running the Compiled Assembly

What we’re interested in is result.CompiledAssembly - in order to run it we have to create an instance of the method that serves as entrypoint and then invoking it.

Short note: if the assembly that you’re trying to run belongs to a Console Application and this program has the same project type, you might need to call FreeConsole() and then AllocConsole(). Without recreating the Console there seems to be no output from the compiled assembly.

This is how we can run the compiled code:


Assembly assembly = result.CompiledAssembly;

//taking the entrypoint
MethodInfo methodInfo = assembly.EntryPoint;

// creating an instance
object entryPointInstance = assembly.CreateInstance(methodInfo.Name);

// then invoking it with no arguments (hence the 'null')
methodInfo.Invoke(entryPointInstance, null);

3. Encrypting & Attaching Sourcecodes

This is one of the tough parts - we take the sourcecodes of the files that we want to secure and encrypt them (I use AES with Rijndael’s algorithm) then attach the results at the end of the executable that we’ve been working on at the previous steps.

Here, the content of the executable and each sourcecode are separated by a sequence of 3 FS (File Separator Character). It’s not the clean way to handle this…don’t use it in serious projects; but for this tutorial it should be fine.

FS = 28(dec) = 1C(hex);

The method that I use looks like this:


static void appendContents(String fileName)
{
    // fileName contains the name of the decrypter 
    FileStream fstream = new FileStream(fileName, FileMode.Append);
    
    // attaching the first 3 FS chars
    fstream.WriteByte(CHAR_FS);
    fstream.WriteByte(CHAR_FS);
    fstream.WriteByte(CHAR_FS);

    // grabbing any .cs file (anything that needs to be encrypted and attached)
    string[] sourceFiles = Directory.GetFiles(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "*.cs", SearchOption.AllDirectories);

    // taking each source file
    for (int i = 0; i < sourceFiles.Length; i++)
    {
        byte[] buffer = File.ReadAllBytes(sourceFiles[i]);
                

        // removing UTF8's byte order mark, if needed
        if (buffer.Length > 2 && buffer[0] == 0xEF && buffer[1] == 0xBB && buffer[2] == 0XBF)
        {
            // skipping the first 3 bytes
            byte[] newBuffer = new byte[buffer.Length - 3];
            Array.Copy(buffer, 3, newBuffer, 0, buffer.Length - 3);

            // encrypting with a test key
            newBuffer = EncryptMessage(newBuffer, "abcdabcdabcdabcdabcdabcdabcdabcd");

            // writing...
            fstream.Write(newBuffer, 0, newBuffer.Length);
        }
        else
        {
            // same thing as above, but for texts without BOM
            buffer = EncryptMessage(buffer, "abcdabcdabcdabcdabcdabcdabcdabcd");
            fstream.Write(buffer, 0, buffer.Length);
        }

        // more separators!
        fstream.WriteByte(CHAR_FS);
        fstream.WriteByte(CHAR_FS);
        fstream.WriteByte(CHAR_FS);
    }
}

I’ll not add EncryptMessage()’s code here since it’s not related to the actual subject - you can find it below, in the complete sourcecode.

4. Extracting & Decrypting Sourcecodes

Procedure that runs before the whole compile & run thingy - we look for any sequence of 3 FS characters, skip the executable’s content, take the encrypted sourcecode and run it through the decryption method - the result is pure C# code that will be given to the compiler.

Remember to replace the "abcdabcdabc..." decryption key with what the user inputs in order to use the program (like a license) - line 31.


static String[] getContents()
{
    // reads all the bytes found in the running executable's file
    byte[] bytes = File.ReadAllBytes(Assembly.GetEntryAssembly().Location);

    int i = 0;
            
    List<String> sourceFiles = new List<String>();

    // skipping the original executable's data
    for (i = 0; i < bytes.Length - 2; i++)
    {
        // if there are 3 FS characters in a row
        // then there's a source file
        if (bytes[i] == bytes[i + 1] && bytes[i + 1] == bytes[i + 2] && bytes[i + 2] == 28)
        {
            i += 3;
            break;
        }
    }

    // here I should keep one sourcefile at a time
    List<Byte> sourceFileBuffer = new List<Byte>(4000);

    for (; i < bytes.Length - 2; i++)
    {
        // checking if I reached the end of a sourcefile
        if (bytes[i] == bytes[i + 1] && bytes[i + 1] == bytes[i + 2] && bytes[i + 2] == 28)
        {
            // TO DO: decrypt with the key given by the user of the program
            sourceFiles.Add(Encoding.Default.GetString(DecryptMessage(sourceFileBuffer.ToArray(), "abcdabcdabcdabcdabcdabcdabcdabcd")));
            sourceFileBuffer.Clear();
            i += 2;
        }
        else
            sourceFileBuffer.Add(bytes[i]);
    }

    // returning the array of sourcecodes
    return sourceFiles.ToArray();
}

Final Notes & Complete Sourcecode

Below you’ll find the sourcecode I ended up with while writing this article. It’s more like a fast way to explain an idea - it needs some “patching”.

In order to actually use it you should split this into 2 programs - one for encrypting and attaching and the other to do the decryption, compilation & execution. You send only the latter one to the user - so he won’t get the encryption key - this or switch to an asymmetric algorithm. Also don’t forget to remove the hardcoded decryption key and ask the user for his own.


using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;

namespace ConsoleApplication1
{
    class Program
    {
        const int CHAR_FS = 28;

        public static byte[] EncryptMessage(byte[] text, string key)
        {
            RijndaelManaged aes = new RijndaelManaged();
            aes.KeySize = 256;
            aes.BlockSize = 256;
            aes.Padding = PaddingMode.Zeros;
            aes.Mode = CipherMode.CBC;

            aes.Key = Encoding.Default.GetBytes(key);
            aes.GenerateIV();

            string IV = Encoding.Default.GetString(aes.IV);

            ICryptoTransform AESEncrypt = aes.CreateEncryptor(aes.Key, aes.IV);
            byte[] buffer = text;

            return Encoding.Default.GetBytes(Encoding.Default.GetString(AESEncrypt.TransformFinalBlock(buffer, 0, buffer.Length)) + IV);
        }

        public static byte[] DecryptMessage(byte[] text, string key)
        {
            RijndaelManaged aes = new RijndaelManaged();
            aes.KeySize = 256;
            aes.BlockSize = 256;
            aes.Padding = PaddingMode.Zeros;
            aes.Mode = CipherMode.CBC;

            aes.Key = Encoding.Default.GetBytes(key);

            byte[] IV = new byte[32];
            Array.Copy(text, text.Length - 32, IV, 0, 32);

            byte[] text2 = new byte[text.Length - 32];
            Array.Copy(text, text2, text2.Length);

            aes.IV = IV;

            ICryptoTransform AESDecrypt = aes.CreateDecryptor(aes.Key, aes.IV);

            return AESDecrypt.TransformFinalBlock(text2, 0, text2.Length);
        }

        static void appendContents(String fileName)
        {
            FileStream fstream = new FileStream(fileName, FileMode.Append);
            fstream.WriteByte(CHAR_FS);
            fstream.WriteByte(CHAR_FS);
            fstream.WriteByte(CHAR_FS);


            string[] sourceFiles = Directory.GetFiles(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "*.cs", SearchOption.AllDirectories);


            for (int i = 0; i < sourceFiles.Length; i++)
            {
                byte[] buffer = File.ReadAllBytes(sourceFiles[i]);
                

                // removing UTF8's byte order mark...
                if (buffer.Length > 2 && buffer[0] == 0xEF && buffer[1] == 0xBB && buffer[2] == 0XBF)
                {
                    byte[] newBuffer = new byte[buffer.Length - 3];
                    Array.Copy(buffer, 3, newBuffer, 0, buffer.Length - 3);

                    newBuffer = EncryptMessage(newBuffer, "abcdabcdabcdabcdabcdabcdabcdabcd");

                    fstream.Write(newBuffer, 0, newBuffer.Length);
                }
                else
                {
                    buffer = EncryptMessage(buffer, "abcdabcdabcdabcdabcdabcdabcdabcd");

                    fstream.Write(buffer, 0, buffer.Length);
                }

                fstream.WriteByte(CHAR_FS);
                fstream.WriteByte(CHAR_FS);
                fstream.WriteByte(CHAR_FS);
            }
        }
        static String[] getContents()
        {
            byte[] bytes = File.ReadAllBytes(Assembly.GetEntryAssembly().Location);

            int i = 0;
            
            List<String> sourceFiles = new List<String>();

            for (i = 0; i < bytes.Length - 2; i++)
            {
                if (bytes[i] == bytes[i + 1] && bytes[i + 1] == bytes[i + 2] && bytes[i + 2] == 28)
                {
                    i += 3;
                    break;
                }
            }

            List<Byte> sourceFileBuffer = new List<Byte>(4000);

            for (; i < bytes.Length - 2; i++)
            {
                if (bytes[i] == bytes[i + 1] && bytes[i + 1] == bytes[i + 2] && bytes[i + 2] == 28)
                {
                    sourceFiles.Add(Encoding.Default.GetString(DecryptMessage(sourceFileBuffer.ToArray(), "abcdabcdabcdabcdabcdabcdabcdabcd")));
                    sourceFileBuffer.Clear();
                    i += 2;
                }
                else
                    sourceFileBuffer.Add(bytes[i]);

            }
            return sourceFiles.ToArray();
        }

        static void Main(string[] args)
        {
            CodeDomProvider provider = CodeDomProvider.CreateProvider("CSharp");

            CompilerParameters parameters = new CompilerParameters();

            parameters.GenerateExecutable = true;
            parameters.GenerateInMemory = true;
            parameters.TreatWarningsAsErrors = false;
            

            parameters.ReferencedAssemblies.Add("System.Windows.Forms.dll");
            parameters.ReferencedAssemblies.Add("System.dll");
            parameters.ReferencedAssemblies.Add("System.Drawing.dll");

            if (args.Length > 0)
            {
                appendContents(args[0]);
                return;
            }

            // source-files
            CompilerResults result = provider.CompileAssemblyFromSource(parameters, getContents());


            if (result.Errors.Count > 0)
            {
                foreach (CompilerError er in result.Errors)
                    Console.WriteLine(er.ToString());

                Console.ReadLine();
                return;
            }

            Assembly assembly = result.CompiledAssembly;
            MethodInfo methodInfo = assembly.EntryPoint;

            object entryPointInstance = assembly.CreateInstance(methodInfo.Name);
            methodInfo.Invoke(entryPointInstance, null);
        }
    }
}