COFFI
1.2
               
Getting Started with COFFI

This guide assumes that you are a little bit familiar with the COFF binary file format. See External documentation for more details on the COFF binary file format.

COFF File Reader

The COFFI library is just normal C++ header files. In order to use all its classes and types, simply include the main header file "coffi.hpp". All COFFI library declarations reside in a namespace called "COFFI". This can be seen in the following example (the full source file is in examples/tutorial/tutorial.cpp):

2 #include <iostream>
3 #include <coffi/coffi.hpp>
4 
5 using namespace COFFI;
6 
7 int main(int argc, char** argv)
8 {
The COFFI library include file.
COFFI library namespace.
Definition: coffi.hpp:66

This section of the tutorial will explain how to work with the reader portion of the COFFI library. The first step would be creating a coffi class instance. The coffi constructor has no parameters. The creation is normally followed by invoking the 'load' member method, passing it an COFF file name as a parameter:

  • Create the coffi class instance
  • Initialize the instance by loading a COFF file. The function load() returns true if the COFF file was found and processed successfully. It returns false otherwise
16  // Create an coffi reader
17  coffi reader;
18 
19  // Load COFF data
20  if (!reader.load(argv[1])) {
21  std::cout << "Can't find or process COFF file " << argv[1] << std::endl;
22  return 2;
23  }

All the COFF file header properties such as the architecture are accessible now. To get the architecture of the file use:

27  // Print COFF file properties
28  std::cout << "COFF file architecture: ";
29  switch (reader.get_architecture()) {
31  if (reader.get_optional_header()) {
32  if (reader.get_optional_header()->get_magic() ==
34  std::cout << "Portable Executable PE32+" << std::endl;
35  }
36  else {
37  std::cout << "Portable Executable PE32" << std::endl;
38  }
39  }
40  else {
41  std::cout << "Portable Executable" << std::endl;
42  }
43  break;
45  std::cout << "CEVA" << std::endl;
46  break;
48  std::cout << "Texas Instruments" << std::endl;
49  break;
50  }
#define OH_MAGIC_PE32PLUS
PE32+ format.
Definition: coffi_types.hpp:92
@ COFFI_ARCHITECTURE_PE
Windows portable executable (PE or PE+)
@ COFFI_ARCHITECTURE_CEVA
CEVA Inc.
@ COFFI_ARCHITECTURE_TI
Texas Instruments.
Note
The COFF types, flags and constants are defined in the coffi_types.hpp header file. This file is included automatically into the project. For example: OH_MAGIC_PE32, OH_MAGIC_PE32PLUS constants define values for PE32/PE32+ formats.

COFF binary files might consist of (some items are optional):

  • File headers
  • Data directories (for the for PE architecture only), including:
    • Import/export information
    • Resource information,
    • Etc.
  • Sections, including:
    • A header
    • Some raw data
    • Relocations entries
    • Line number entries
  • A symbol table
  • A strings table

Each section has its own responsibility: some contains executable code, others program's data, and so on. See COFF binary format documentation for your specific architecture for purpose and content description of sections and segments. The following code demonstrates how to find out the amount of sections the COFF file contains. The code also presents how to access particular section properties like names and sizes:

  • The get_sections() member of COFFI's reader object allows to retrieve the number of sections inside a given COFF file. It could also be used to get access to individual sections by using the subscript operator[], which returns a pointer to the corresponding section's interface.
  • Use the C++ range-based for loop to loop through the sections given by get_sections(). Sections can also be addressed with the subscript operator[], by its number or symbolic name.
  • get_index(), get_name() and get_data_size() are member functions of the section class.
54  // Print the COFF file sections info
55  auto sec_num = reader.get_sections().size();
56  std::cout << "Number of sections: " << sec_num << std::endl;
57  for (auto sec : reader.get_sections()) {
58  std::cout << " [" << sec->get_index() << "] " << sec->get_name()
59  << "\t" << sec->get_data_size() << std::endl;
60  }

COFF Data Accessors

To simplify creation and interpretation of specific COFF elements, the COFFI library provides accessor classes.

Currently, the following classes are available:

COFF item accessed Class Basic structure
(maps directly to the the file)
Applicable for
architecture
MS-DOS header dos_header msdos_header PE
COFF file header coff_header_impl coff_file_header PE, CEVA
coff_header_impl_ti coff_file_header_ti TI
COFF optional file header optional_header_impl_pe coff_optional_header_pe PE, CEVA
optional_header_impl_pe_plus coff_optional_header_pe_plus PE
optional_header_impl_ti common_optional_header_ti TI
Windows NT file header win_header_impl<win_header_pe> win_header_pe PE
win_header_impl<win_header_pe_plus> win_header_pe_plus PE
Section header section_impl section_header PE, CEVA
section_impl_ti section_header_ti TI
Data directory directory image_data_directory PE
Relocation entry relocation rel_entry PE
rel_entry_ceva CEVA
rel_entry_ti TI
Symbol symbol symbol_record
See also:
All
PE

See also the macros for accessing the COFF structures fields.

COFFDump Utility

The source code for the COFFDump Utility can be found in the examples directory. An example of output is presented below.

COFF File Writer

Writing the program

In this section we will create a simple COFF executable file that prints out the classical "Hello, World!" message.

The executable will be created and run on a Windows platform. It is supposed to run well on both 32 and 64-bit Windows platforms. The full source file is in examples/writer/writer.cpp.

The file will be created without invoking the compiler or assembler tools in the usual way (i.e. translating high level source code that makes use of the standard library functions).

Instead, using the COFFI writer, all the necessary sections and data of the file will be created and filled explicitly, each, with its appropriate data. The physical file would then be created by the COFFI library.

Before starting, implementations details of coffi that users should be aware of are:

  • The coffi class, while constructing, creates the string table section automatically.
  • The coffi class computes and updates the offsets, sizes, etc., either while constructing, or when saving the file. The coffi class tries to keep the various sections and other elements in the same order as given by the user.

Our usage of the library API will consist of several steps:

  • Creating an empty coffi object
  • Setting-up COFF file properties
  • Creating code section and data content for it
  • Creating data section and its content
  • Addition of both sections to corresponding COFF file segments
  • Setting-up the program's entry point
  • Dumping the coffi object to an executable COFF file

Initialize an empty coffi object. This should be done as the first step when creating a new coffi object, because the other API is relying on parameter provided (COFF file architecture).

15 #include <coffi/coffi.hpp>
16 
17 using namespace COFFI;
18 
19 // clang-format off
20 
21 void write_the_file( const std::string &filename )
22 {
23  coffi writer;
24 
25  // You can't proceed without this function call!
The COFFI library's main class.
Definition: coffi.hpp:73
void create(coffi_architecture_t architecture)
Cleans and/or initializes the coffi object.
Definition: coffi.hpp:353

Initialize an optional header.

30  // Create the optional header (required for images *.exe, *.dll)
31  writer.create_optional_header();
void create_optional_header(uint16_t magic=OH_MAGIC_PE32)
Initializes an optional header for the coffi object.
Definition: coffi.hpp:389

Create a new section, set section's attributes. Section type, flags and alignment have a big significance and controls how this section is treated by a linker or OS loader. This code section contains the code that:

  • Sets the MessageBoxA parameters
  • Calls MessageBoxA
  • Sets the ExitProcess parameter
  • Calls ExitProcess

The ExitProcess and MessageBoxA functions are imported from the Windows DLL at adresses 0x40304C and 0x403054, see IAT (import address table) below.

35  // Create code section
36  section* text_sec = writer.add_section( ".text" );
37  char text[] = {
38  '\x6A', '\x00', // push 0
39  '\x68', '\x00', '\x20', '\x40', '\x00', // push offset 0x00402000 (string in the .rdata section)
40  '\x68', '\x00', '\x20', '\x40', '\x00', // push offset 0x00402000 (string in the .rdata section)
41  '\x6A', '\x00', // push 0
42  '\xE8', '\x0E', '\x00', '\x00', '\x00', // call 0x401021
43  '\x6A', '\x00', // push 0
44  '\xE8', '\x01', '\x00', '\x00', '\x00', // call 0x40101b
45  '\xF4', // hlt
46  '\xFF', '\x25', '\x4C', '\x30', '\x40', '\x00', // 0x40101b: jmp *0x40304C (ExitProcess)
47  '\xFF', '\x25', '\x54', '\x30', '\x40', '\x00' // 0x401021: jmp *0x403054 (MessageBoxA)
48  };
49  text_sec->set_data( text, sizeof( text ) );
50  text_sec->set_virtual_address(0x1000);
51  text_sec->set_virtual_size(sizeof(text));
section * add_section(const std::string &name)
Add a COFF section.
Definition: coffi.hpp:488
#define IMAGE_SCN_CNT_CODE
The section contains executable code.
#define IMAGE_SCN_MEM_EXECUTE
The section can be executed as code.
#define IMAGE_SCN_ALIGN_4BYTES
Align data on a 4 - byte boundary.Valid only for object files.
#define IMAGE_SCN_MEM_READ
The section can be read.

Add a data section, storing the overwhelmingly impressive message.

56  // Create a .rdata section, with the string
57  section* rdata_sec = writer.add_section( ".rdata" );
58  char rdata[] = "Hello World!\0";
59  rdata_sec->set_data( rdata, sizeof( rdata ) );
60  rdata_sec->set_virtual_address(0x2000);
61  rdata_sec->set_virtual_size(sizeof(rdata));
#define IMAGE_SCN_CNT_INITIALIZED_DATA
The section contains initialized data.

Add a data section, used by Windows for importing DLL (run-time link). Refer to the Windows documentation for more details. The code below imports ExitProcess and MessageBoxA from KERNEL32.dll and USER32.dll respectively.

66  // Create a .idata section
67  section* idata_sec = writer.add_section( ".idata" );
68  uint8_t idata[] = {
69  // RVA = 0x3000
70 
71  // Import table (array of IMAGE_IMPORT_DESCRIPTOR structures)
72  0x3C, 0x30, 0x00, 0x00, // OriginalFirstThunk: offset of the import lookup table
73  0x00, 0x00, 0x00, 0x00, // TimeDateStamp
74  0x00, 0x00, 0x00, 0x00, // ForwarderChain
75  0x5C, 0x30, 0x00, 0x00, // Name: Offset of "KERNEL32.dll"
76  0x4C, 0x30, 0x00, 0x00, // FirstThunk: offset of import address table
77 
78  // Import table (array of IMAGE_IMPORT_DESCRIPTOR structures)
79  0x44, 0x30, 0x00, 0x00, // OriginalFirstThunk: offset of the import lookup table
80  0x00, 0x00, 0x00, 0x00, // TimeDateStamp
81  0x00, 0x00, 0x00, 0x00, // ForwarderChain
82  0x6C, 0x30, 0x00, 0x00, // Name: Offset of "USER32.dll"
83  0x54, 0x30, 0x00, 0x00, // FirstThunk: offset of import address table
84 
85  // Empty IMAGE_IMPORT_DESCRIPTOR structure (signaling the end of the IMAGE_IMPORT_DESCRIPTOR array)
86  0x00, 0x00, 0x00, 0x00, // OriginalFirstThunk
87  0x00, 0x00, 0x00, 0x00, // TimeDateStamp
88  0x00, 0x00, 0x00, 0x00, // ForwarderChain
89  0x00, 0x00, 0x00, 0x00, // Name
90  0x00, 0x00, 0x00, 0x00, // FirstThunk
91 
92  // Import lookup table (array of IMAGE_THUNK_DATA structures)
93  // RVA = 0x303C
94  0x7C, 0x30, 0x00, 0x00, // Import function by name
95  0x00, 0x00, 0x00, 0x00, //
96  // RVA = 0x3044
97  0x8C, 0x30, 0x00, 0x00, // Import function by name
98  0x00, 0x00, 0x00, 0x00, //
99 
100  // Import address table (array of IMAGE_THUNK_DATA structures)
101  // RVA = 0x304C
102  0x7C, 0x30, 0x00, 0x00, // Import function by name
103  0x00, 0x00, 0x00, 0x00, //
104  // RVA = 0x3054
105  0x8C, 0x30, 0x00, 0x00, // Import function by name
106  0x00, 0x00, 0x00, 0x00, //
107 
108  // Names
109  // RVA = 0x305C
110  'K', 'E', 'R', 'N', 'E', 'L', '3', '2', '.', 'd', 'l', 'l', 0, 0, 0, 0,
111  // RVA = 0x306C
112  'U', 'S', 'E', 'R', '3', '2', '.', 'd', 'l', 'l', 0, 0, 0, 0, 0, 0,
113 
114  // Hint/Name Table
115  // RVA = 0x307C
116  0x5E, 0x01, 'E', 'x', 'i', 't', 'P', 'r', 'o', 'c', 'e', 's', 's', 0, 0, 0,
117  // RVA = 0x308C
118  0x7F, 0x02, 'M', 'e', 's', 's', 'a', 'g', 'e', 'B', 'o', 'x', 'A', 0, 0, 0,
119  };
120 
121  idata_sec->set_data( (char*)idata, sizeof( idata ) );
122  idata_sec->set_virtual_address(0x3000);
123  idata_sec->set_virtual_size(sizeof(idata));

Add a relocation section, used by Windows for loading the file. The relocation entries identify the places in the code section where there are addresses to be relocated.

128  // Create a .reloc section
129  section* reloc_sec = writer.add_section( ".reloc" );
130  uint16_t reloc[] = {
131  0x1000, 0x0000, // RVA of relocation block
132  0x0010, 0x0000, // Size of the relocation block
133  0x3003, // Relocation HIGHLOW at address 0x1003
134  0x3008, // Relocation HIGHLOW at address 0x1008
135  0x301D, // Relocation HIGHLOW at address 0x101D
136  0x3023, // Relocation HIGHLOW at address 0x1023
137  };
138  reloc_sec->set_data( (char*)reloc, sizeof( reloc ) );
139  reloc_sec->set_virtual_address(0x4000);
140  reloc_sec->set_virtual_size(sizeof(reloc));

Update the file headers

145  // Set headers properties
147  writer.get_optional_header()->set_code_size(0x200);
148  writer.get_optional_header()->set_initialized_data_size(0x400);
149  writer.get_optional_header()->set_entry_point_address(0x00001000);
150  writer.get_optional_header()->set_code_base(0x00001000);
151  writer.get_optional_header()->set_data_base(0x00002000);
152  writer.get_win_header()->set_image_base(0x00400000);
153  writer.get_win_header()->set_section_alignment(0x1000);
154  writer.get_win_header()->set_major_os_version(4);
155  writer.get_win_header()->set_major_image_version(1);
156  writer.get_win_header()->set_major_subsystem_version(4);
157  writer.get_win_header()->set_image_size(0x5000);
158  writer.get_win_header()->set_headers_size(0x200);
159  writer.get_win_header()->set_subsystem(3); // Windows CUI
160  writer.get_win_header()->set_stack_reserve_size(0x00100000);
161  writer.get_win_header()->set_stack_commit_size(0x1000);
162  writer.get_win_header()->set_heap_reserve_size(0x100000);
163  writer.get_win_header()->set_heap_commit_size(0x1000);
win_header * get_win_header()
Returns the Windows NT header.
Definition: coffi.hpp:463
optional_header * get_optional_header()
Returns the optional COFF header.
Definition: coffi.hpp:448
coff_header * get_header()
Returns the COFF header.
Definition: coffi.hpp:436
#define IMAGE_FILE_EXECUTABLE_IMAGE
Image only. Indicates that the image file is valid and can be run. If this flag is not set,...
#define IMAGE_FILE_32BIT_MACHINE
Machine based on 32-bit-word architecture.

Update the data directories, especially those pointing to the DLL import information and relocation information.

167  // Add directories (required for images *.exe, *.dll)
168  writer.add_directory(image_data_directory{0, 0}); // Export Directory [.edata (or where ever we found it)]
169  writer.add_directory(image_data_directory{idata_sec->get_virtual_address(), idata_sec->get_virtual_size()}); // Import Directory [parts of .idata]
170  writer.add_directory(image_data_directory{0, 0}); // Resource Directory [.rsrc]
171  writer.add_directory(image_data_directory{0, 0}); // Exception Directory [.pdata]
172  writer.add_directory(image_data_directory{0, 0}); // Security Directory
173  writer.add_directory(image_data_directory{reloc_sec->get_virtual_address(), reloc_sec->get_virtual_size()}); // Base Relocation Directory [.reloc]
174  writer.add_directory(image_data_directory{0, 0}); // Debug Directory
175  writer.add_directory(image_data_directory{0, 0}); // Description Directory
176  writer.add_directory(image_data_directory{0, 0}); // Special Directory
177  writer.add_directory(image_data_directory{0, 0}); // Thread Storage Directory [.tls]
178  writer.add_directory(image_data_directory{0, 0}); // Load Configuration Directory
179  writer.add_directory(image_data_directory{0, 0}); // Bound Import Directory
180  writer.add_directory(image_data_directory{0x304C, 0x10}); // Import Address Table Directory
181  writer.add_directory(image_data_directory{0, 0}); // Delay Import Directory
182  writer.add_directory(image_data_directory{0, 0}); // CLR Runtime Header
183  writer.add_directory(image_data_directory{0, 0}); // Reserved
directory * add_directory(const image_data_directory &rva_and_size)
Add a PE data directory.
Definition: coffi.hpp:525

Save the COFF binary file on disk:

187  // Recompute all the offsets in the file
188  writer.layout();
189 
190  // Create the PE file
191  writer.save( filename );
bool save(const std::string &file_name)
Creates a file in COFF binary format.
Definition: coffi.hpp:310
void layout()
Performs the layout of the file.
Definition: coffi.hpp:581
Note
The coffi library takes care of the resulting binary file layout calculation. It does this on base of the provided memory image addresses and sizes. It is the user's responsibility to provide correct values for these parameters. Please refer to the documentation of the COFF architectures for specific requirements related to the executable COFF file attributes and/or mapping.

Similarly to the COFF File Reader example, you may use provided accessor classes to interpret and modify content of section's data.

Testing the program

Let's compile the example above (see the complete source file examples/writer/writer.cpp) into an executable file (named writer.exe). Invoking writer.exe will create the executable file hello.exe that prints the overwhelmingly impressive "Hello, World!" message.

The listing below works with GCC installed:

C:\the_user_path\COFFI\examples\writer>dir /B *.cpp
writer.cpp
C:\the_user_path\COFFI\examples\writer>g++ writer.cpp -o writer.exe -I../..
C:\the_user_path\COFFI\examples\writer>writer.exe
C:\the_user_path\COFFI\examples\writer>dir /B *.exe
hello.exe
writer.exe
C:\the_user_path\COFFI\examples\writer>hello.exe

The same thing with Visual C++:

C:\the_user_path\COFFI\examples\writer>vcvars32.bat
**********************************************************************
** Visual Studio 2019 Developer Command Prompt v16.6.2
** Copyright (c) 2020 Microsoft Corporation
**********************************************************************
[vcvarsall.bat] Environment initialized for: 'x86'
C:\the_user_path\COFFI\examples\writer>cl writer.cpp /I../.. /EHsc /nologo
writer.cpp
C:\the_user_path\COFFI\examples\writer>writer.exe
C:\the_user_path\COFFI\examples\writer>hello.exe

In case you already compiled the COFFDump Utility, you can inspect the properties of the produced executable file:

C:\the_user_path\COFFI\examples\writer>..\COFFDump\COFFDump.exe hello.exe > hello.dump
C:\the_user_path\COFFI\examples\writer>type hello.dump

Will result in:

Dump of file hello.exe
File Header
Machine: 014C
Number of Sections: 0004
TimeDateStamp: 00000000 (1970-01-01 01:00:00)
PointerToSymbolTable: 00000000
NumberOfSymbols: 00000000
SizeOfOptionalHeader: 00E0
Characteristics: 0102
Optional Header
Magic 010B
linker version 0.0
size of code 0200
size of initialized data 00000400
size of uninitialized data 00000000
entrypoint RVA 00001000
base of code 00001000
base of data 00002000
image base 0000000000400000
section align 00001000
file align 00000200
required OS version 4.0
image version 1.0
subsystem version 4.0
Win32 Version 0
size of image 00005000
size of headers 00000200
checksum 00003B01
Subsystem 0003
DLL flags 0000
stack reserve size 0000000000100000
stack commit size 0000000000001000
heap reserve size 0000000000100000
heap commit size 0000000000001000
RVAs & sizes 00000010
Data Directory
EXPORT_TABLE rva: 00000000 size: 00000000
IMPORT_TABLE rva: 00003000 size: 0000009C
RESOURCE_TABLE rva: 00000000 size: 00000000
EXCEPTION_TABLE rva: 00000000 size: 00000000
CERTIFICATE_TABLE rva: 00000000 size: 00000000
BASE_RELOCATION_TABLE rva: 00004000 size: 00000010
DEBUG rva: 00000000 size: 00000000
ARCHITECTURE rva: 00000000 size: 00000000
GLOBAL_PTR rva: 00000000 size: 00000000
TLS_TABLE rva: 00000000 size: 00000000
LOAD_CONFIG_TABLE rva: 00000000 size: 00000000
BOUND_IMPORT rva: 00000000 size: 00000000
IAT rva: 0000304C size: 00000010
DELAY_IMPORT_DESCRIPTOR rva: 00000000 size: 00000000
COMPLUS_RUNTIME_HEADER rva: 00000000 size: 00000000
RESERVED rva: 00000000 size: 00000000
Section Table
000 .text VirtSize: 00000027 VirtAddr: 00001000
raw data offs: 00000400 raw data size: 00000200
relocation offs: 00000000 relocations: 00000000
line # offs: 00000000 line #'s: 00000000
characteristics: 60300020
CODE EXECUTE READ ALIGN_4BYTES
001 .rdata VirtSize: 0000000E VirtAddr: 00002000
raw data offs: 00000600 raw data size: 00000200
relocation offs: 00000000 relocations: 00000000
line # offs: 00000000 line #'s: 00000000
characteristics: 40300040
INITIALIZED_DATA READ ALIGN_4BYTES
002 .idata VirtSize: 0000009C VirtAddr: 00003000
raw data offs: 00000800 raw data size: 00000200
relocation offs: 00000000 relocations: 00000000
line # offs: 00000000 line #'s: 00000000
characteristics: 40300040
INITIALIZED_DATA READ ALIGN_4BYTES
003 .reloc VirtSize: 00000010 VirtAddr: 00004000
raw data offs: 00000A00 raw data size: 00000010
relocation offs: 00000000 relocations: 00000000
line # offs: 00000000 line #'s: 00000000
characteristics: 40300040
INITIALIZED_DATA READ ALIGN_4BYTES