【问题标题】:How to generate an exe with data that can be easily modified by a script如何使用可以通过脚本轻松修改的数据生成 exe
【发布时间】:2016-08-25 05:42:06
【问题描述】:

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

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

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

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

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

【问题讨论】:

  • 一个未签名的可执行文件仍然会在附加任意数据的情况下运行。我不确定签名的可执行文件,但我认为它仍然有效 - 您只需要小心将额外数据视为不受信任,因为恶意第三方可以修改它而不影响签名。
  • 这有of an XY problem的所有标记。你要解决的真正问题是什么?不,不是你要问的。但真正的问题,你相信谁的解决方案是你问什么。
  • @SamVarshavchik 这个问题几乎正是我想要解决的问题。我们的 EXE 是一个客户端服务器应用程序,我想在安装客户端时动态设置预配置的服务器 URL,而不必为每个服务器构建单独的安装程序。如果我可以动态更新安装程序,问题就解决了
  • 如果只是需要配置的URL,真的没有办法简单地做mysetup.exe /url="the url string"这样的事情吗??

标签: c++ windows-installer exe


【解决方案1】:

你是如何在 Linux 服务器上首先确定服务器 url 的?

在客户端/服务器设置中,使用某种“登录”来执行此操作要容易得多。这是典型的解决方案,即使“登录”实际上只是一个代码、他们在安装程序中键入的标识符、他们的电子邮件地址,甚至是带有任何密码的客户端 IP 地址。

几个例子:

  • 如果您希望每个客户端都有一个唯一标识符,那么嵌入到 EXE 中可能无济于事。如果用户之间共享 EXE 会怎样?
  • 如果 EXE 打算在一组人之间共享,并且具有相同的标识符,那么当有人丢失了他们的副本并想要重新获得它时会发生什么?
  • 如果您尝试对服务器进行负载平衡,您不能让客户端 ping 一个 URL 的负载平衡器吗?

如果您将逻辑构建到您的客户端和服务器中(可能使用网络服务器或“大厅服务器”作为实际服务器的入口),您将获得更多的控制权和安全性。如果您希望它持续存在,您甚至可以将结果保存到客户端应用程序的配置文件中。

这里的另一个好处是这种方法还支持其他平台。通过将其构建为协议,您可以使其跨平台。

如果您无法更改实际应用程序,您也可以使用“启动器”或其他东西,它运行初始握手,输出配置文件,然后启动实际应用程序。

【讨论】:

  • 所以我们有几十个不相关的 URL(每个客户),并且希望客户端预配置指向正确的 URL。用户仍需输入凭据,但服务器 URL 已预先配置。这样我们就不必处​​理用户输入错误 URL 等的支持开销。
  • 这就是为什么你让他们输入一些简单的东西,然后你把它翻译成你身边的 URL。如果您对将数据嵌入到 EXE 中一无所知,我仍然认为这是错误的方式,您可能需要重新签署 EXE,请参见此处:stackoverflow.com/questions/18287960/…
  • 在一般情况下,在安装期间(或首次运行)需要在线登录或代码不是一个好的解决方案,因为最终用户可能希望在不支持的计算机上运行安装程序有互联网连接。我是一名专业的系统管理员,必须在互联网上安装的软件对我来说是一个主要的麻烦。 (在这种特殊情况下,该软件似乎无论如何都需要访问互联网,所以这可能不是问题。)
【解决方案2】:

当我找到另一个针对博客文章的 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;
}

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

【讨论】:

猜你喜欢
  • 2010-12-11
  • 1970-01-01
  • 2015-03-02
  • 1970-01-01
  • 2011-03-07
  • 2019-07-11
  • 1970-01-01
  • 1970-01-01
  • 2019-06-21
相关资源
最近更新 更多