我希望生成一个Windows EXE(尽管我最终需要支持Mac / Linux),它包含两个文件,一个配置文件和一个MSI。我将exe开始关闭MSI,然后将配置文件复制到位。我不完全确定如何做到这一点,但我并不太关心。
但是,我的一个要求是配置文件必须可以使用在Linux服务器上运行的脚本(Ruby)进行修改,因为我需要在下载EXE时更改一些数据。
我已经看了几种方法,例如使用xd
生成我在项目中包含的字节流,但这似乎是一个糟糕的解决方案。也许不是,这是正确的解决方案,但我想确定。这样做有“正确”的方法吗?
是否可以简单地将数据附加到可执行文件的末尾并使用C ++进行搜索?
我不是在寻找一个完整的解决方案,只需要知道最适合这个应用程序的技术是什么,所以我可以弄清楚如何实现它们。我的搜索结果很少。
答案 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地址。
一些例子:
如果您在客户端和服务器中构建逻辑(可能使用Web服务器或“大厅服务器”作为实际服务器的条目),您将获得更多控制和安全性。如果您希望它保持不变,您甚至可以将其结果保存到客户端应用程序的配置文件中。
此处的另一个好处是此方法还支持其他平台。通过将其构建为协议,您可以跨平台。
如果您无法更改实际的应用程序,您还可以使用“启动器”或其他内容运行初始握手,输出配置文件,然后启动实际应用程序。