c++ - 如何生成包含可通过脚本轻松修改的数据的 exe

标签 c++ windows-installer exe

我希望生成一个包含两个文件的 Windows EXE(尽管我最终需要支持 Mac/Linux),一个配置文件和一个 MSI。我会让 exe 启动 MSI,然后将配置文件复制到位。我不太确定如何执行此操作,但我不太担心。

但是,我的要求之一是配置文件必须可以使用在 Linux 服务器上运行的脚本 (Ruby) 进行修改,因为我需要在下载 EXE 时更改一些数据。

我已经研究了几种实现方法,例如使用 xd 生成我包含在我的项目中的字节流,但这似乎是一个糟糕的解决方案。也许不是,那是正确的解决方案,但我想确定。有这样做的“正确”方法吗?

是否可以简单地将数据附加到可执行文件的末尾并使用 C++ 查找它?

我不是在这里寻找完整的解决方案,只需要知道调用最适合此应用程序的技术是什么,这样我就可以弄清楚如何实现它们。我的搜索结果很少。

最佳答案

一旦我找到博客文章的另一个 SO 答案点,我就想出了一个解决方案:https://blog.barthe.ph/2009/02/22/change-signed-executable/

这是我的 Ruby 代码:

# Class used to append data to the end of a Windows Portable Executable (PE)
# without invalidating the Windows Digital Signature. Byte offset of the payload
# is added to the end of the file as an unsigned int.
#
# The way Microsoft authenticode works is the following. During the signature
# process, it computes the hash on the executable file. The hash is then used to
# make a digital certificate which is authenticated by some authority. This
# certificate is attached to the end of the PE exectuable, in a dedicated
# section called the Certificate Table. When the executable is loaded, Windows
# computes the hash value, and compares it to the one attached to the
# Certificate table. It is “normally” impossible to change anything in the file
# without breaking the digital authentication.
#
# However three areas of a PE executable are excluded from the hash computation:
#
#  - The checksum in the optional Windows specific header. 4 bytes
#  - The certificate table entry in the optional Windows specific header. 8 bytes
#  - The Digital Certificate section at the end of the file. Variable length
#
# You should be able to change those area without breaking the signature. It is
# possible to append an arbitrary amount of data at the end of the Digital
# Certificate. This data is ignored by both the signature parsing and hash
# computation algorithms. It works on all version of Window as long as the
# length of the Certificate Table is correctly increased. The length is stored
# in two different location: the PE header and the beginning of the certificate
# table.
#
# Original Source: https://blog.barthe.ph/2009/02/22/change-signed-executable/
class ExeAppender
  # Portable Executable file format magic constants
  PE_OFFSET_OFFSET = 0x3c
  PE_HEADER = 0x00004550

  # Unix Common Object File Format magic constants
  COFF_OPT_LENGTH_OFFSET = 20
  COFF_OPT_OFFSET = 24
  COFF_MAGIC = 0x10b
  COFF_CHECKSUM_OFFSET = 64

  # PE Certificate Table magic constants
  CERT_OFFSET_OFFSET = 128
  CERT_LENGTH_OFFSET = 132

  def initialize(filename)
    @filename = filename
    @file = File.binread(@filename)
  end

  # Append data to the EXE, updating checksums and digital certificate tables if
  # needed.
  def append(data)
    data     += [@file.bytesize].pack('V')
    pe_offset = read_uint8(@file, PE_OFFSET_OFFSET)

    unless read_uint32(@file, pe_offset) == PE_HEADER
      raise StandardError.new("No valid PE header found")
    end

    if read_uint16(@file, pe_offset + COFF_OPT_LENGTH_OFFSET) == 0
      raise StandardError.new("No optional COFF header found")
    end

    unless read_uint16(@file, pe_offset + COFF_OPT_OFFSET) == COFF_MAGIC
      raise StandardError.new("PE format is not PE32")
    end

    cert_offset = read_uint16(@file, pe_offset + COFF_OPT_OFFSET + CERT_OFFSET_OFFSET)

    if cert_offset > 0
      # Certificate table found, modify certificate lengths
      cert_length = read_uint32(@file, pe_offset + COFF_OPT_OFFSET + CERT_LENGTH_OFFSET)

      unless read_uint32(@file, cert_offset) != cert_length
        raise StandardError.new("Certificate length does not match COFF header")
      end

      new_length = cert_length + data.length
      write_uint_32(@file, new_length, pe_offset + COFF_OPT_OFFSET + CERT_LENGTH_OFFSET)
      write_uint_32(@file, new_length, cert_offset)
    end

    # Calculate and update checksum of end result
    @file += data
    offset = pe_offset + COFF_OPT_OFFSET + COFF_CHECKSUM_OFFSET
    write_uint_32(@file, checksum, offset)
  end

  # Write the modified EXE to a file
  def write(filename=nil)
    filename = @filename unless filename
    File.binwrite(filename, @file)
  end

  private

  # http://stackoverflow.com/questions/6429779/can-anyone-define-the-windows-pe-checksum-algorithm
  def checksum
    limit = 2**32
    checksum = 0

    (0..@file.bytesize).step(4).each do |i|
      next if (i + 4) > @file.bytesize
      val       = read_uint32(@file, i)
      checksum += val
      checksum  = (checksum % limit) + (checksum / limit | 0) if checksum >= limit
    end

    if @file.bytesize % 4 > 0
      trailer = @file[(@file.bytesize - (@file.bytesize % 4))..@file.bytesize]

      (1..(4 - @file.bytesize % 4)).each do
        trailer << 0
      end

      val       = read_uint32(trailer, 0)
      checksum += val
      checksum  = (checksum % limit) + (checksum / limit | 0) if checksum >= limit
    end

    checksum = unsigned_right_shift(checksum, 16) + (checksum & 0xffff)
    checksum = unsigned_right_shift(checksum, 16) + checksum

    (checksum & 0xffff) + @file.bytesize
  end

  def unsigned_right_shift(val, shift_by)
    mask = (1 << (32 - shift_by)) - 1
    (val >> shift_by) & mask
  end

  # Read 8 bit unsigned little endian integer
  def read_uint8(str, offset)
    str[offset..(offset + 2)].unpack('C')[0]
  end

  # Read 16 bit unsigned little endian integer
  def read_uint16(str, offset)
    str[offset..(offset + 2)].unpack('v')[0]
  end

  # Read 32 bit unsigned little endian integer
  def read_uint32(str, offset)
    str[offset..(offset + 4)].unpack('V')[0]
  end

  # Write 32 bit unsigned little endian integer
  def write_uint_32(str, int, offset)
    str[offset..(offset + 3)] = [int].pack('V')
  end
end

我这样调用它:

exe = ExeAppender.new('ConsoleApplication1.exe')
exe.append('This is some arbitrary data appended to the end of the PDF. Woo123')
exe.write('ConsoleApplication.exe')

我的 C++ 应用程序如下所示:

#include "stdafx.h"
#include <iostream>
#include <fstream>
#include <iterator>
#include <vector>
#include <Windows.h>

using namespace std;

int main() {
    cout << "Hello World\n";

    int offset       = 0;
    int file_size    = 0;
    int payload_size = 0;
    wchar_t filename[MAX_PATH];

    // Get the path to myself
    GetModuleFileName(NULL, filename, MAX_PATH);
    wcout << "Reading self: " << filename << "\n";

    // Open self and find payload offset
    ifstream myfile;
    myfile.open(filename);
    myfile.seekg(-4, ios_base::end);
    myfile.read((char*)&offset, 4);

    // Calculate payload size and create a buffer to hold it
    file_size    = myfile.tellg();
    payload_size = file_size - offset - 4;
    char *buf = new char[payload_size + 1];

    cout << "File size: " << file_size << "\n";
    cout << "Read byte offset: " << offset << "\n";
    cout << "Payload Size: " << payload_size << "\n";

    // Read the payload
    myfile.seekg(offset);
    myfile.read(buf, payload_size);
    buf[payload_size] = '\0';
    myfile.close();

    myfile.close();
    cout << "Payload: '" << buf << "'\n";

    return 0;
}

一切正常,数字签名仍然有效。

关于c++ - 如何生成包含可通过脚本轻松修改的数据的 exe,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36948886/

相关文章:

Wix 创建多语言 msi

用于两个或多个 python 文件(模块)的 Python cx_Freeze

java - launch4j 编译的 java 应用程序可执行文件被防病毒软件阻止

java - 使用launch4j和maven时不会创建.exe

c++ - C++ 程序员不知道以 null 结尾的字符串是如何工作的,这是否可以接受?

c++ - 字符串比较 - 替换字符数组中的单词

c# - 卸载非安装程序创建的文件

wix - 有条件地将单个 WiX 属性设置为不同的值

c++ - 如何在 C++ 中创建一个允许多态返回不同类型的工厂方法?

c++ - Qt GUI 应用程序在与 gui 交互时停止实时进程