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

Moo*_*ker 8 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);
Run Code Online (Sandbox Code Playgroud)

但由于某种原因,我正在复制几乎整个文件,而不是最后几千字节.是因为编译器优化,在类似的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);
    }
Run Code Online (Sandbox Code Playgroud)

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

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

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

Ark*_*kun 3

最简单的解决方案是更换

const string magic = "MAGICNUMBER";
Run Code Online (Sandbox Code Playgroud)

static string magic = "magicnumber".ToUpper();
Run Code Online (Sandbox Code Playgroud)

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

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

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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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