4

I'm looking to generate a Windows EXE (although I'll need to support Mac/Linux eventually) that contains two files, a config file and an MSI. I'll have the exe kick off the MSI, then copy the config file in place. I'm not entirely sure how to do this, but I'm not too concerned.

However, one of my requirements is that the config file must be modifiable with a script (Ruby) running on a Linux server, as I need to change some data whenever the EXE is downloaded.

I've looked at a few ways of doing it, such as using xd to generate a byte stream that I include in my project, but that seems like a poor solution. Maybe not, and thats the correct solution but I want to be sure. Is there a "correct" way of doing this?

Is it possible to simply append the data to the end of the executable and seek to it with C++?

I'm not looking for a full solution here, just need to know what the techniques best suited to this application are called so I can figure out how to implement them. My searches have yielded few results.

Brandon
  • 16,382
  • 12
  • 55
  • 88
  • An unsigned executable will still run with arbitrary data appended to it. I'm not certain about signed executables, but I *think* it still works - you just have to be careful to treat the extra data as untrusted, since a malicious third party could modify it without affecting the signature. – Harry Johnston Apr 29 '16 at 23:40
  • 1
    This has all the markings [of an XY problem](http://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). What is the real problem you're trying to solve? No, not what you're asking about. But the real problem whose solution you believe is what you're asking about. – Sam Varshavchik Apr 29 '16 at 23:45
  • @SamVarshavchik The question is pretty much exactly what I'm trying to solve. Our EXE is a client server application, and I want to dynamically set the preconfigured server URL when the client is installed, without having to build separate installers for every server. If I can dynamically update the installer, problem solved – Brandon Apr 30 '16 at 01:47
  • If it's just a URL that needs configuring, is there really no way to simply do something like mysetup.exe /url="the url string" ?? – PhilDW Apr 30 '16 at 17:22

2 Answers2

8

I figured out a solution once I found another SO answer point towards a blog article: https://blog.barthe.ph/2009/02/22/change-signed-executable/

Here is my Ruby code:

# 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

I call it like this:

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

My C++ application looks like this:

#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;
}

Everything works great and the digital signatures are still valid.

Brandon
  • 16,382
  • 12
  • 55
  • 88
  • Sadly this technique seems to no longer be valid https://learn.microsoft.com/en-us/security-updates/securitybulletins/2013/ms13-098 – TheNextman Oct 05 '18 at 21:46
-1

How are you determining the server url in the first place on the Linux server?

In a client/server setup, it's much easier to use some sort of "login" to perform this operation. This is the typical solution, even if that "login" is actually just a code, identifier they type in the installer, their email address, or even client IP address with any password.

A few examples:

  • If you're looking to have each client have a unique identifier, embedding in the EXE may not help. What if users share EXEs between each other?
  • If the EXE is meant to be shared among a group of people, with the same identifier, what happens when someone loses their copy and wants to re-obtain it?
  • If you're trying to load balance servers, can't you just have the client ping a load balancer for a URL?

If you build the logic into your client and server (possibly using a webserver or a "lobby server" as an entry to the actual server), you'll gain more control and security. You can even save the result of this into a config file from your client app, if you want it to persist.

The other benefit here is that this method also supports other platforms. By building it as a protocol, you make it cross platform.

If you're not able to change the actual app, you can also use a "launcher" or something, which runs the initial handshake, outputs a config file, and then launches the actual application.

Todd Christensen
  • 1,297
  • 8
  • 11
  • So we have dozens have unrelated URLs (per customer), and want the client preconfigured to point to the correct url. Users still have to enter credentials, but the server URL is preconfigured. This way we don't have to deal with the support overhead of users typing the URL wrong, etc. – Brandon Apr 30 '16 at 12:18
  • That's why you have them type something simple, and you translate it to a URL from your side. If you're dead set on embedding data into the EXE, which I still contend is the wrong way, you'll probably want to re-sign the EXE, see here: http://stackoverflow.com/questions/18287960/signing-windows-application-on-linux-based-distros – Todd Christensen Apr 30 '16 at 15:43
  • In the general case, requiring an online logon or code during installation (or on first run) isn't a good solution because the end user may want to run the installer on a machine that doesn't have an internet connection. I'm a professional sysadmin, and software that has to be on the internet to install is a major hassle for me. (In this particular case it looks like the software requires internet access anyway, so it probably wouldn't be a problem.) – Harry Johnston Apr 30 '16 at 22:03