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):
7 int main(
int argc,
char** argv)
The COFFI library include file.
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
20 if (!reader.load(argv[1])) {
21 std::cout <<
"Can't find or process COFF file " << argv[1] << std::endl;
All the COFF file header properties such as the architecture are accessible now. To get the architecture of the file use:
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;
37 std::cout <<
"Portable Executable PE32" << std::endl;
41 std::cout <<
"Portable Executable" << std::endl;
45 std::cout <<
"CEVA" << std::endl;
48 std::cout <<
"Texas Instruments" << std::endl;
#define OH_MAGIC_PE32PLUS
PE32+ format.
@ 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.
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;
COFF Data Accessors
To simplify creation and interpretation of specific COFF elements, the COFFI library provides accessor classes.
Currently, the following classes are available:
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).
17 using namespace COFFI;
21 void write_the_file(
const std::string &filename )
The COFFI library's main class.
void create(coffi_architecture_t architecture)
Cleans and/or initializes the coffi object.
Initialize an optional header.
void create_optional_header(uint16_t magic=OH_MAGIC_PE32)
Initializes an optional header for the coffi object.
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.
39 '\x68',
'\x00',
'\x20',
'\x40',
'\x00',
40 '\x68',
'\x00',
'\x20',
'\x40',
'\x00',
42 '\xE8',
'\x0E',
'\x00',
'\x00',
'\x00',
44 '\xE8',
'\x01',
'\x00',
'\x00',
'\x00',
46 '\xFF',
'\x25',
'\x4C',
'\x30',
'\x40',
'\x00',
47 '\xFF',
'\x25',
'\x54',
'\x30',
'\x40',
'\x00'
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.
#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.
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.
67 section* idata_sec = writer.
add_section(
".idata" );
72 0x3C, 0x30, 0x00, 0x00,
73 0x00, 0x00, 0x00, 0x00,
74 0x00, 0x00, 0x00, 0x00,
75 0x5C, 0x30, 0x00, 0x00,
76 0x4C, 0x30, 0x00, 0x00,
79 0x44, 0x30, 0x00, 0x00,
80 0x00, 0x00, 0x00, 0x00,
81 0x00, 0x00, 0x00, 0x00,
82 0x6C, 0x30, 0x00, 0x00,
83 0x54, 0x30, 0x00, 0x00,
86 0x00, 0x00, 0x00, 0x00,
87 0x00, 0x00, 0x00, 0x00,
88 0x00, 0x00, 0x00, 0x00,
89 0x00, 0x00, 0x00, 0x00,
90 0x00, 0x00, 0x00, 0x00,
94 0x7C, 0x30, 0x00, 0x00,
95 0x00, 0x00, 0x00, 0x00,
97 0x8C, 0x30, 0x00, 0x00,
98 0x00, 0x00, 0x00, 0x00,
102 0x7C, 0x30, 0x00, 0x00,
103 0x00, 0x00, 0x00, 0x00,
105 0x8C, 0x30, 0x00, 0x00,
106 0x00, 0x00, 0x00, 0x00,
110 'K',
'E',
'R',
'N',
'E',
'L',
'3',
'2',
'.',
'd',
'l',
'l', 0, 0, 0, 0,
112 'U',
'S',
'E',
'R',
'3',
'2',
'.',
'd',
'l',
'l', 0, 0, 0, 0, 0, 0,
116 0x5E, 0x01,
'E',
'x',
'i',
't',
'P',
'r',
'o',
'c',
'e',
's',
's', 0, 0, 0,
118 0x7F, 0x02,
'M',
'e',
's',
's',
'a',
'g',
'e',
'B',
'o',
'x',
'A', 0, 0, 0,
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.
129 section* reloc_sec = writer.
add_section(
".reloc" );
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
win_header * get_win_header()
Returns the Windows NT header.
optional_header * get_optional_header()
Returns the optional COFF header.
coff_header * get_header()
Returns the COFF header.
#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.
169 writer.
add_directory(image_data_directory{idata_sec->get_virtual_address(), idata_sec->get_virtual_size()});
173 writer.
add_directory(image_data_directory{reloc_sec->get_virtual_address(), reloc_sec->get_virtual_size()});
directory * add_directory(const image_data_directory &rva_and_size)
Add a PE data directory.
Save the COFF binary file on disk:
191 writer.
save( filename );
bool save(const std::string &file_name)
Creates a file in COFF binary format.
void layout()
Performs the layout of the file.
- 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