使用C#进行自解压可执行

时间:2013-02-07 14:36:41

标签: c# windows exe archive

我正在使用幻数创建简单的自解压存档来标记内容的开头。 现在它是一个文本文件:

  

MAGICNUMBER ....文本文件的内容

接下来,将textfile复制到可执行文件的末尾:

  

复制programm.exe / b + textfile.txt / b sfx.exe

我正在尝试使用以下代码找到第二次出现的幻数(第一次显然是硬编码常量):

    string my_filename = System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName;
    StreamReader file = new StreamReader(my_filename);
    const int block_size = 1024;
    const string magic = "MAGICNUMBER";
    char[] buffer = new Char[block_size];
    Int64 count = 0;
    Int64 glob_pos = 0;
    bool flag = false;
    while (file.ReadBlock(buffer, 0, block_size) > 0)
    {
        var rel_pos = buffer.ToString().IndexOf(magic);
        if ((rel_pos > -1) & (!flag))
        {
            flag = true;
            continue;
        }

        if ((rel_pos > -1) & (flag == true))
        {
            glob_pos = block_size * count + rel_pos;
            break;
        }
        count++;
    }



    using (FileStream fs = new FileStream(my_filename, FileMode.Open, FileAccess.Read))
    {
        byte[] b = new byte[fs.Length - glob_pos];
        fs.Seek(glob_pos, SeekOrigin.Begin);
        fs.Read(b, 0, (int)(fs.Length - glob_pos));
        File.WriteAllBytes("c:/output.txt", b);

但由于某种原因,我正在复制几乎整个文件,而不是最后几千字节。是因为编译器优化,在类似的while循环中内联魔术常量?

我应该如何正确地进行自我提取存档?

猜测我应该向后读取文件以避免编译器内联魔法常数倍数的问题。 所以我用以下方式修改了我的代码:

    string my_filename = System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName;
    StreamReader file = new StreamReader(my_filename);
    const int block_size = 1024;
    const string magic = "MAGIC";
    char[] buffer = new Char[block_size];
    Int64 count = 0;
    Int64 glob_pos = 0;
    while (file.ReadBlock(buffer, 0, block_size) > 0)
    {
        var rel_pos = buffer.ToString().IndexOf(magic);
        if (rel_pos > -1)
        {
            glob_pos = block_size * count + rel_pos;
        }
        count++;
    }



    using (FileStream fs = new FileStream(my_filename, FileMode.Open, FileAccess.Read))
    {
        byte[] b = new byte[fs.Length - glob_pos];
        fs.Seek(glob_pos, SeekOrigin.Begin);
        fs.Read(b, 0, (int)(fs.Length - glob_pos));
        File.WriteAllBytes("c:/output.txt", b);
    }

所以我扫描了所有文件一次,发现我虽然是最后一次出现的幻数并从这里复制到它的末尾。虽然这个程序创建的文件看起来比以前的尝试要小,但它并不是我附加到“自解压”存档的同一个文件。为什么?

我的猜测是,由于二进制转换为字符串,所以附加文件开头的位置计算错误。如果是这样,我应如何修改我的位置计算以使其正确?

另外我应该如何选择幻数然后使用真实文件,例如pdf?我无法轻易修改pdfs以包含预定义的幻数。

4 个答案:

答案 0 :(得分:4)

试一试。一些C#Stream IO 101:

    public static void Main()
    {
        String path = @"c:\here is your path";

        // Method A: Read all information into a Byte Stream
        Byte[] data = System.IO.File.ReadAllBytes(path);
        String[] lines = System.IO.File.ReadAllLines(path);

        // Method B: Use a stream to do essentially the same thing. (More powerful)
        // Using block essentially means 'close when we're done'. See 'using block' or 'IDisposable'.
        using (FileStream stream = File.OpenRead(path))
        using (StreamReader reader = new StreamReader(stream))
        {
            // This will read all the data as a single string
            String allData = reader.ReadToEnd();
        }

        String outputPath = @"C:\where I'm writing to";

        // Copy from one file-stream to another
        using (FileStream inputStream = File.OpenRead(path))
        using (FileStream outputStream = File.Create(outputPath))
        {
            inputStream.CopyTo(outputStream);

            // Again, this will close both streams when done.
        }

        // Copy to an in-memory stream
        using (FileStream inputStream = File.OpenRead(path))
        using (MemoryStream outputStream = new MemoryStream())
        {
            inputStream.CopyTo(outputStream);

            // Again, this will close both streams when done.
            // If you want to hold the data in memory, just don't wrap your 
            // memory stream in a using block.
        }

        // Use serialization to store data.
        var serializer = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();

        // We'll serialize a person to the memory stream.
        MemoryStream memoryStream = new MemoryStream();
        serializer.Serialize(memoryStream, new Person() { Name = "Sam", Age = 20 });

        // Now the person is stored in the memory stream (just as easy to write to disk using a 
        // file stream as well.

        // Now lets reset the stream to the beginning:
        memoryStream.Seek(0, SeekOrigin.Begin);

        // And deserialize the person
        Person deserializedPerson = (Person)serializer.Deserialize(memoryStream);

        Console.WriteLine(deserializedPerson.Name); // Should print Sam

    }

    // Mark Serializable stuff as serializable.
    // This means that C# will automatically format this to be put in a stream
    [Serializable]
    class Person
    {
        public String Name { get; set; }
        public Int32 Age { get; set; }
    }

答案 1 :(得分:3)

您可以将压缩文件作为资源添加到项目本身:

项目>特性
enter image description here

将此资源的属性设置为Binary

然后,您可以使用

检索资源
byte[] resource = Properties.Resources.NameOfYourResource;

答案 2 :(得分:3)

最简单的解决方案是替换

const string magic = "MAGICNUMBER";

static string magic = "magicnumber".ToUpper();

但整个魔术弦方法存在更多问题。什么是包含魔术字符串的文件?我认为最好的解决方案是将文件大小放在文件之后。这种提取方式更容易:从最后一个字节读取长度,并从文件末尾读取所需的字节数。

更新:除非您的文件非常大,否则这应该有效。 (在这种情况下,您需要使用一对旋转缓冲区(以小块方式读取文件)):

string inputFilename = System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName;
string outputFilename = inputFilename + ".secret";
string magic = "magic".ToUpper();

byte[] data = File.ReadAllBytes(inputFilename);
byte[] magicData = Encoding.ASCII.GetBytes(magic);

for (int idx = magicData.Length - 1; idx < data.Length; idx++) {
    bool found = true;
    for (int magicIdx = 0; magicIdx < magicData.Length; magicIdx++) {
        if (data[idx - magicData.Length + 1 + magicIdx] != magicData[magicIdx]) {
            found = false;
            break;
        }
    }
    if (found) {
        using (FileStream output = new FileStream(outputFilename, FileMode.Create)) {
            output.Write(data, idx + 1, data.Length - idx - 1);
        }
    }
}

Update2 :这应该快得多,使用少量内存并处​​理所有大小的文件,但程序必须是正确的可执行文件(大小为512字节的倍数):

string inputFilename = System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName;
string outputFilename = inputFilename + ".secret";
string marker = "magic".ToUpper();

byte[] data = File.ReadAllBytes(inputFilename);
byte[] markerData = Encoding.ASCII.GetBytes(marker);
int markerLength = markerData.Length;

const int blockSize = 512; //important!

using(FileStream input = File.OpenRead(inputFilename)) {
    long lastPosition = 0;
    byte[] buffer = new byte[blockSize];
    while (input.Read(buffer, 0, blockSize) >= markerLength) {
        bool found = true;
        for (int idx = 0; idx < markerLength; idx++) {
            if (buffer[idx] != markerData[idx]) {
                found = false;
                break;
            }
        }
        if (found) {
            input.Position = lastPosition + markerLength;
            using (FileStream output = File.OpenWrite(outputFilename)) {
                input.CopyTo(output);
            }
        }
        lastPosition = input.Position;
    }
}

在这里阅读一些方法:http://www.strchr.com/creating_self-extracting_executables

答案 3 :(得分:2)

向后搜索而不是向前搜索(假设您的文件不会包含所说的幻数)。

或者附加你的(文本)文件,然后是它的长度(或原始exe的长度),所以你只需要读取最后一个DWORD /几个字节来查看文件的长度 - 那么就不需要幻数

更强大,将文件存储为可执行文件中的附加数据部分。没有外部工具,这更加繁琐,因为它需要知道用于NT可执行文件的q.v.的PE文件格式。 http://msdn.microsoft.com/en-us/library/ms809762.aspx