Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use C# exe to modify resources of a different C# exe

Solved! See below.

I have 2 C# applications. Application a is supposed to modify the internal resources of application b. Application b is supposed to do something with its (modified) resources when executed.

How can I accomplish that?

Here is what I tried:

public static void addFileToResources(string dest, string src)
{
    Assembly a_dest = Assembly.LoadFile(dest);

    using (Stream s_dest = a_dest.GetManifestResourceStream("Elevator.Properties.Resources.resources"))
    {
        using (ResourceWriter rw = new ResourceWriter(s_dest))
        {
            byte[] b_src = File.ReadAllBytes(src);
            rw.AddResource("target", b_src);
        }
    }
}

I get a System.ArgumentException with The stream is readonly. on System.Resources.ResourceWriter..ctor(Stream stream)

EDIT
Since that does not seem to be possible with .net resources: Is there any other way?
I want to produce a single file (namely the exe of application b) which is executable and can work with the data (stored in the exe) that is given prior to execution from application a. Preferably without having to actually compile b in order to give it the data.

Assumptions in order to make it a little easier:

  • a is always executed before b
  • a is only executed once
  • both applications are written by me

Edit - Sollution
Since it is not possible to accomplish that via resources I used the following workaround:
Apparently you can append ANYTHING to an exe file and it will still be executable, so here is what I came up with:

public class Packer : IDisposable
{
    // chosen quite arbitrarily; can be anything you'd like but should be reasonably unique
    private static byte[] MAGIC_NUMBER = { 0x44, 0x61, 0x6c, 0x65, 0x6b, 0x4c, 0x75, 0x63 };

    private Stream inStream;

    public Packer(string filename, bool openReadonly = false)
    {
        // The FileAccess.Read is necessary when I whant to read from the file that is being executed.
        // Hint: To get the path for the executing file I used:
        // System.Reflection.Assembly.GetExecutingAssembly().Location
        inStream = File.Open(filename, FileMode.Open, openReadonly ? FileAccess.Read : FileAccess.ReadWrite, openReadonly ? FileShare.Read : FileShare.None);
    }

    public byte[] ReadData(int index)
    {
        byte[] mn_buf = new byte[MAGIC_NUMBER.Length];
        byte[] len_buf = new byte[sizeof(Int32)];
        int data_len = 0;
        inStream.Seek(0, SeekOrigin.End);
        for (int i = 0; i <= index; ++i)
        {
            // Read the last few bytes
            inStream.Seek(-MAGIC_NUMBER.Length, SeekOrigin.Current);
            inStream.Read(mn_buf, 0, MAGIC_NUMBER.Length);
            inStream.Seek(-MAGIC_NUMBER.Length, SeekOrigin.Current);
            for (int j = 0; j < MAGIC_NUMBER.Length; ++j)
            {   // Check if the last bytes are equals to my MAGIC_NUMBER
                if (mn_buf[j] != MAGIC_NUMBER[j])
                {
                    throw new IndexOutOfRangeException("Not enough data.");
                }
            }
            inStream.Seek(-sizeof(Int32), SeekOrigin.Current);
            inStream.Read(len_buf, 0, sizeof(Int32));
            inStream.Seek(-sizeof(Int32), SeekOrigin.Current);
            // Read the length of the data
            data_len = BitConverter.ToInt32(len_buf, 0);
            inStream.Seek(-data_len, SeekOrigin.Current);
        }
        byte[] data = new byte[data_len];
        // Read the actual data and return it
        inStream.Read(data, 0, data_len);
        return data;
    }

    public void AddData(byte[] data)
    {
        // append it
        inStream.Seek(0, SeekOrigin.End);
        inStream.Write(data, 0, data.
        inStream.Write(BitConverter.GetBytes(data.Length), 0, sizeof(Int32));
        inStream.Write(MAGIC_NUMBER, 0, MAGIC_NUMBER.Length);
    }

    public void Dispose()
    {
        inStream.Dispose();
    }
}

If you want to use this snippet go ahead but note that if you add data to a file the indexes are in reverse order when retrieving:
Let's say you write data set A first and then data set B, if you read the data later B will have the index 0 and A the index 1.

like image 451
Ch33f Avatar asked Sep 29 '22 18:09

Ch33f


1 Answers

Based on your assumptions you can update/add resources of your executable using Mono.Cecil library

Here are three essential methods for Resource Manipulation using Mono.Cecil:

    public static void ReplaceResource(string path, string resourceName, byte[] resource)
    {
        var definition =
            AssemblyDefinition.ReadAssembly(path);

        for (var i = 0; i < definition.MainModule.Resources.Count; i++)
            if (definition.MainModule.Resources[i].Name == resourceName)
            {
                definition.MainModule.Resources.RemoveAt(i);
                break;
            }

        var er = new EmbeddedResource(resourceName, ManifestResourceAttributes.Public, resource);
        definition.MainModule.Resources.Add(er);
        definition.Write(path);
    }

    public static void AddResource(string path, string resourceName, byte[] resource)
    {
        var definition =
            AssemblyDefinition.ReadAssembly(path);

        var er = new EmbeddedResource(resourceName, ManifestResourceAttributes.Public, resource);
        definition.MainModule.Resources.Add(er);
        definition.Write(path);
    }

    public static MemoryStream GetResource(string path, string resourceName)
    {
        var definition =
            AssemblyDefinition.ReadAssembly(path);

        foreach (var resource in definition.MainModule.Resources)
            if (resource.Name == resourceName)
            {
                var embeddedResource =(EmbeddedResource) resource;
                var stream = embeddedResource.GetResourceStream();

                var bytes = new byte[stream.Length];
                stream.Read(bytes, 0, bytes.Length);

                var memStream = new MemoryStream();
                memStream.Write(bytes,0,bytes.Length);
                memStream.Position = 0;
                return memStream;
            }

        return null;
    }

You can use GetResource method to retrieve current resource stream (Writable),

using ResourceWriter, ResourceReader or ResourceEditor classes you can read/write or modify current resource or create a new resource, then simply put it back in the executable by calling ReplaceResource or add it as a new one by calling AddResource

Here is an example of replacing an image in resource (by creating a new resource from scratch):

            var ms = new MemoryStream();
            var writer = new ResourceWriter(ms);
            writer.AddResource("good_luck",new Bitmap("good_luck.png"));
            writer.Generate();   
            ReplaceResource(@"my executale.exe", "ResourceTest.Properties.Resources.resources",ms.ToArray());

you can obtain Cecil through PM> Install-Package Mono.Cecil nuget.

like image 155
user3473830 Avatar answered Oct 03 '22 00:10

user3473830