如何使用脚本

时间:2016-04-29 23:18:28

标签: c++ windows-installer exe

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

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

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

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

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

2 个答案:

答案 0 :(得分:5)

我找到了一个解决方案,一旦我找到另一个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;
}

一切都很好,数字签名仍然有效。

答案 1 :(得分:0)

如何在Linux服务器上首先确定服务器URL?

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

一些例子:

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

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

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

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