Writing custom data to executable files in Windows and Linux

By: Other Guy

Abstract: Think you can't tweak with your project after it's compiled? Check out these useful techniques for adding custom data to your EXE in Win32 and Linux. By Daniel Polistchuck.

The code for this article is available at CodeCentral. Find it here

Writing custom data to EXE files in Windows and Linux

We developers are used to treating the executable code generated by our development tools as "black boxes." Nevertheless, there are times when the ability to write and read custom data to and from executable files would be really useful. Some interesting solutions can make use of the technique described in this article, including:

  • software Licensing
  • software customization
  • security info
  • post-compilation/linking addition of data to programs
  • stubs
  • installation tools

How are executable files organized?

Each operating system defines its own executable-file internal format. We will be dealing here with Linux, which uses the industry-standard ELF (Executable and Linking Format) format and with Win32 operating systems that implement the Microsoft PE (Portable Executable) standard. Although the two formats differ in their inner structures, each can be seen as a self-contained, internally-referenced "file system." Both formats define a header that is divided into several parts. Some of these parts (the Section Header in PE and Program Header in ELF) work much like file allocation tables in FAT file systems: They describe the internal offsets to chunks of data and code in the executable file.

When a user or shell activates a file that is recognized as an executable file in Linux or Win32, the "loader" springs into action. The loader is responsible for loading the file from the disk, mapping it to memory, interpreting the file internal structures (headers and such), and starting the execution of the executable initial entry-point.

Where do we put the custom data?

Take a look at the picture above. Bearing in mind that the executable file works like a self-containing file system that is read from its beginning to its end, it is safe to assume that whatever is put beyond the end of the executable file won't compromise the executable information. So the best place to put custom data is after the EOF of the original executable file.

The Win32 and Linux Loaders happily ignore our CUSTOM DATA chunk.

So we can write anything to the executable?

Yes, that's correct. We can write anything past the EOF of an executable file in Linux and Win32. We are free to define any kind of structure and deal with it in our code any way we want to.

So let's add a footer to our executable file that will include some useful information:

const
  //our ExeBuffer signature
  ExeBufSig = 'EB1.0';

type
  //the Footer for our executable format
  TExeBufFooter = record
    OriginalSize : Integer;
    Sig : Array[0..4] of char;
  end;
  • The ExeBufSig is a constant that will let us identify our "custom executable format."
  • The TExeBufFooter is a record (yes, a record!) that will help us locate the signature and the custom data we add to the executable.

How do we add custom data to the executable?

Linux uses POSIX file semantics, which lets us modify, delete, and rename a file that is already in use and not locked. But Win32 doesn't let us do that kind of stuff! It's all very complicated, but the bottom line is that Linux lets us write self-modifying executables, but Win32 doesn't.

To deal with that situation, we must write a second executable (the Writer), that will be responsible for writing custom data into the "client" (Reader) executable.

The code that is responsible for writing the data is simple:

procedure SetExeData (ExeName : String; ExeBuf : TExeBuf);
var
  F : File;
  BufSz,OrigSz : Integer;
  Footer : TExeBufFooter;
begin
  AssignFile (F,ExeName);
  Reset (F,1);
  try
    //obtaining the original file size
    OrigSz := FileSize(F);
    //go to the EOF of the file
    Seek (F,OrigSz);
    //Writing our custom data beyond the EOF
    BufSz := Length(ExeBuf);
    BlockWrite (F,Pointer(ExeBuf)^,BufSz);
    //Writing our footer
    FillChar (Footer,SizeOf(Footer),0);
    Footer.OriginalSize := OrigSz;
    Footer.Sig := ExeBufSig;
    BlockWrite (F,Footer,Sizeof(Footer));
  finally
    CloseFile (F);
  end;
end;

Simple, right? We treat the executable like any other file.

How do we read data back from the executable?

Everything we need to read the data is in the TExeBufFooter record located in the end of the executable. The exact location of the the footer is FileSize - SizeOf(TExeBufFooter). Upon reading the footer from the executable, we can obtain the original file size by accessing its OriginalSize field. So we can implement GetExeData like this:

procedure GetExeData (ExeName : String; var ExeBuf : TExeBuf);
var
  F : File;
  CurrSz, BufSize : Integer;
  OldFileMode : Integer;
  Footer : TExeBufFooter;
begin
  AssignFile (F,ExeName);
  //Saving the old FileMode
  OldFileMode := FileMode;
  //Setting the FileMode to ReadOnly
  FileMode := 0;
  try
    Reset (F,1);
    try
      //Getting the current file size
      CurrSz := FileSize (F);
      //Seeking to the footer position
      //and reading it
      Seek (F,CurrSz-SizeOf (Footer));
      BlockRead (F,Footer,Sizeof(Footer));
      //if there's no signature, boom!
      //no data in this executable!
      if Footer.Sig <> ExeBufSig then
        raise EExeBuf.Create ('No Data in EXE!');
      //calculating the buffer size that was written
      //to this executable file
      BufSize :=CurrSz-Footer.OriginalSize-SizeOf(Footer);
      SetLength (ExeBuf,BufSize);
      //seek and read!
      Seek (F,Footer.OriginalSize);
      BlockRead(F,Pointer(ExeBuf)^, BufSize);
    finally
      CloseFile (F);
    end;
  finally
    //returning to the previous saved
    //FileMode
    FileMode := OldFileMode;
  end;
end;

This code may seem a little complicated at first glance, but the only real complication is the part where we calculate the buffer size (BufSize). In fact, it's very simple: We subtract from the current file size the original size and the footer size. Algorithms don't get much simpler than that.

Now we throw in a couple of helper functions to convert TExeBuf to and from Strings:

procedure StringToExeBuf (const S : String; var ExeBuf : TExeBuf);
begin
  SetLength(ExeBuf,Length(S));
  Move (Pointer(S)^,Pointer(ExeBuf)^,Length(S));
end;

function ExeBufToString (const ExeBuf : TExeBuf) : String;
begin
  SetLength (Result,Length(ExeBuf));
  Move (Pointer(ExeBuf)^,Pointer(Result)^,Length(ExeBuf));
end;

And that's it!

Daniel is the IT Director of QualTech IT and is eagerly waiting for the December 19 LOTR Release. He can be reached through e-mail at danpol@pobox.com.


Server Response from: ETNASC03