binfmt-detector-cli: rewrite to support PE32+ binaries (#38)

Rewrite with hard-coded offsets into the PE file format to discern
if a binary is PE32 or PE32+, and then to determine if it contains
a "CLR Data Directory" entry that looks valid.

Tested with PE32 and PE32+ compiled Mono binaries, PE32 and PE32+ native
binaries, and a random assortment of garbage files.

Former-commit-id: 9e7ac86ec84f653a2f79b87183efd5b0ebda001b
This commit is contained in:
Pat Tullmann
2023-10-16 11:16:47 -07:00
committed by GitHub
parent bb231c5b06
commit 0cb742dafb
4772 changed files with 11391249 additions and 2669 deletions

6119
acceptance-tests/Makefile.in Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1 +0,0 @@
78976892a3815bf2dbd924b855d2e86954e20bd7

5185
acceptance-tests/coreclr.mk Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1 +0,0 @@
c2fbd00532f8f1c9f1207b726d6a73dfca590b30

37783
configure vendored Executable file

File diff suppressed because it is too large Load Diff

View File

@ -1 +0,0 @@
e9c659d22140300fc0a43f322bf970c3e5a61d97

7069
configure.ac Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1 +0,0 @@
758460764e4c318dac2a4067aeb789a680d543b5

16979
data/browscap.ini Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1 +0,0 @@
1267e1deb4061a8250ad25125fc7d20fd2ae04ac

8429
debian/changelog vendored Normal file

File diff suppressed because it is too large Load Diff

2638
debian/changelog.1 vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1 +0,0 @@
6d1c20ee9757b2a92bcc007e152259bad9d010a7

View File

@ -1 +0,0 @@
46a48a6e202b27293c0b7d471118ac14b7551962

3166
debian/control vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1 +0,0 @@
71b54f7953a3cd0ed44bf6df3735963f152a0227

View File

@ -1,4 +1,4 @@
binfmt-detector-cli: binfmt-detector-cli.c cil-coff.h binfmt-detector-cli: binfmt-detector-cli.c
$(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) binfmt-detector-cli.c -o binfmt-detector-cli $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) binfmt-detector-cli.c -o binfmt-detector-cli
strip binfmt-detector-cli strip binfmt-detector-cli

View File

@ -1,45 +1,28 @@
binfmt-pe binfmt-detector-cli
This utility determines the Microsoft PE executable file's This utility determines the Microsoft PE executable file's
type (Native, .NET CLR) and runs it using the appropriate type (Native or .NET CLR).
runtime (WINE, CLI). It also detects and refuses to run
MS-DOS (non-PE) executables. It is inteded to be used as a filter a Linux binfmt
configuration, since binfmt itself is incapable of reliably
It is inteded to be used in a Linux binfmt configuration, distinguishing between various PE file types (since they have
since binfmt itself is incapable of reliably distinguishing no different "magic string").
between various PE file types (since they have no different
"magic string") and runtimes refuse to run files which
they don't support (CLR runtimes refuse to run
Native images and vice versa).
Technical information Technical information
The file's type is determined from certain characteristics The file's type is determined from certain characteristics
in the PE / COFF file header. It should be noted that the in the PE / COFF file header. It should be noted that the
techniques used might not be standard and are not throughtly techniques used might not be standard and are not throughtly
tested to work, so false detections might occur. tested to work, so false detections might occur.
In short:
- An MS-DOS executable is assumed if the PE offset in the MS-DOS
header is NULL or points to an offset beyond the file's length
- A CLR file is assumed if the PE header's directory entry
for "CLI header" is not NULL and points to a valid offset in
the executable file.
- A native executable file is assumed otherwise.
The runtime names are hardcoded into the utilit (that is -
not configurable) and are exec'ed from the utility --
"wine" is used for native images, "cli" is used for CLR images.
Credits To be considered a CLR executable, the PE header's directory
entry for the "CLR Header" must have a non-zero address and
a non-zero size.
This utility is based on the PE / COFF header structures from Original Author
Ximian's Mono .NET runtime project ( http://www.go-mono.com/ ).
Author
Ilya Konstantinov <future@shiny.co.il> Ilya Konstantinov <future@shiny.co.il>
Licenses Licenses
This utility is covered by the GPL license. This utility is covered by the GPL license.

View File

@ -13,87 +13,169 @@
* along with this program; if not, write to the Free Software * along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
* *
* Based on:
* binfmt PE executable helper, by Ilya Konstantinov <future@shiny.co.il> * binfmt PE executable helper, by Ilya Konstantinov <future@shiny.co.il>
* Based on PE headers structures courtesy of Mono .NET runtime project
* (http://www.go-mono.com).
*/ */
#include <errno.h> #include <errno.h>
#include <string.h>
#include <stdio.h> #include <stdio.h>
#include <locale.h>
#include <stdlib.h> #include <stdlib.h>
#include <libintl.h>
#include <stdint.h> #include <stdint.h>
#include "cil-coff.h" #define LOG_ENABLED 0
#define LOG(fmt, ...) \
if (LOG_ENABLED) { \
fprintf(stderr, fmt "\n" \
__VA_OPT__(,) __VA_ARGS__ ); \
}
//Change this to one MSDOS, MSDOS, NATIVE or CLR
#define DETECT CLR
#define _(String) gettext(String) /* Read a big-endian, 16-bit value at the given offset from the given file. */
uint16_t fread_uint16(FILE* f, unsigned long offset)
{
if (fseek(f, offset, SEEK_SET) != 0)
{
LOG("Failed to seek to %lu: %d", offset, errno);
exit(EXIT_FAILURE);
}
unsigned char bytes[2];
if (fread(&bytes, sizeof(bytes), 1, f) != 1)
{
LOG("Failed to read %zd bytes at %lu: %d", sizeof(bytes), offset, errno);
exit(EXIT_FAILURE);
}
return bytes[0] | bytes[1] << 8;
}
/* Read a big-endian, 32-bit value at the given offset from the given file. */
uint32_t fread_uint32(FILE* f, unsigned long offset)
{
if (fseek(f, offset, SEEK_SET) != 0)
{
LOG("Failed to seek to %lu: %d", offset, errno);
exit(EXIT_FAILURE);
}
unsigned char bytes[4];
if (fread(&bytes, sizeof(bytes), 1, f) != 1)
{
LOG("Failed to read %zd bytes at %lu: %d", sizeof(bytes), offset, errno);
exit(EXIT_FAILURE);
}
return bytes[0] | bytes[1] << 8 | bytes[2] << 16 | bytes[3] << 24;
}
/* Globals */
enum execTypeEnum {
UNKNOWN,
MSDOS,
NATIVE,
CLR
} execType = UNKNOWN;
int main(int argc, char **argv) int main(int argc, char **argv)
{ {
const char *filename;
FILE *image;
size_t read;
if (argc < 2) exit(EXIT_FAILURE); if (argc < 2) exit(EXIT_FAILURE);
filename = argv[1];
image = fopen(filename, "r");
if (image == NULL) exit(EXIT_FAILURE);
/* Parse the MSDOS header */
{
MSDOSHeader msdos_header;
uint32_t pe_offset;
read = fread(&msdos_header, sizeof(msdos_header), 1, image);
if (read < 1) exit(EXIT_FAILURE);
pe_offset = msdos_header.pe_offset[0]
| msdos_header.pe_offset[1] << 8
| msdos_header.pe_offset[2] << 16
| msdos_header.pe_offset[3] << 24;
if ((pe_offset == 0) ||
(fseek(image, pe_offset, SEEK_SET) != 0))
execType = MSDOS;
}
/* Parse the PE header */
if (execType == UNKNOWN) FILE* image = fopen(argv[1], "r");
if (image == NULL) exit(EXIT_FAILURE);
/*
* Assume the given file is at least an MS-DOS binary (it starts
* with the "MZ" magic). Just need to disambiguate between a
* CLR-managed code binary vs. a traditional native binary.
*/
/* Offset to the PE structured is at fixed offset 0x3C */
const uint32_t pe_offset = fread_uint32(image, 0x3C);
/*
* PE structure should start with the "PE signature" ("PE\0\0", or
* 0x00004550 in big-endian).
*/
const uint32_t pe_signature = fread_uint32(image, pe_offset);
if (pe_signature != 0x004550)
{ {
DotNetHeader dotnet_header; LOG("Wrong PE_signature (got %#08x)", pe_signature);
uint16_t pe_magic; exit(EXIT_FAILURE);
uint32_t rva;
read = fread(&dotnet_header, sizeof(dotnet_header), 1, image);
if (read < 1) exit(EXIT_FAILURE);
pe_magic = dotnet_header.pe.pe_magic[0]
| dotnet_header.pe.pe_magic[1] << 8;
if (dotnet_header.pesig[0] != 'P' || dotnet_header.pesig[1] != 'E' || (pe_magic != 0x10B && pe_magic != 0x20B)) exit(EXIT_FAILURE);
rva = dotnet_header.datadir.pe_cli_header.rva[0]
| dotnet_header.datadir.pe_cli_header.rva[1] << 8
| dotnet_header.datadir.pe_cli_header.rva[2] << 16
| dotnet_header.datadir.pe_cli_header.rva[1] << 24;
if ((dotnet_header.datadir.pe_cli_header.size != 0) &&
(rva != 0) &&
(fseek(image, rva, SEEK_SET) == 0))
execType = CLR;
else
execType = NATIVE;
} }
/*
* The PE format is stored after the 4-byte signature and 20-byte
* COFF header. There are two formats PE32 or PE32+ that might
* contain a CLR-managed binary. All other formats are native.
*/
const uint16_t pe_format = fread_uint16(image, pe_offset + 4 + 20);
/*
* Compute the offset of the "CLR Runtime Header" (the 15th) entry
* in the Header Data Directories section. This offset is
* computed relative to the pe_offset.
*/
size_t datadir_clr_offset = 0;
if (pe_format == 0x10b)
{
LOG("PE32 format");
/*
* +4 bytes for pe_signature
* +20 bytes for COFF header
* +28 bytes for PE32 "standard fields" header
* +68 bytes for "Windows-specific fields" header
* +(8 * 14) bytes for the 15th entry ("CLR Runtime")
* in the Data Directories Header
*/
datadir_clr_offset = 4 + 20 + 28 + 68 + (8 * 14);
}
else if (pe_format == 0x20b)
{
LOG("PE32+ format");
/*
* +4 bytes for pe_signature
* +20 bytes for COFF header
* +24 bytes for PE32 "standard fields" header
* +88 bytes for "Windows-specific fields" header
* +(8 * 14) bytes for the 15th entry ("CLR Runtime")
* in the Data Directories Header
*/
datadir_clr_offset = 4 + 20 + 24 + 88 + (8 * 14);
}
else
{
/*
* This file may be in some other PE format, none of those
* format support CLR-managed binaries, so we are done.
*/
LOG("Not a PE32/PE32+ binary: %#04x", pe_format);
exit(EXIT_FAILURE);
}
LOG("CLR Directory offset %#08zx", datadir_clr_offset);
/*
* The CLR Data Directory entry (like all entries in the Data
* Directory) consists of a 4-byte virtual address and a 4-byte
* size. (In both PE32 and PE32+ formats.)
*/
const uint32_t rva = fread_uint32(image, pe_offset + datadir_clr_offset);
const uint32_t rsz = fread_uint32(image, pe_offset + datadir_clr_offset + 4);
LOG("CLR Data rva=%#0x size=%#0x", rva, rsz);
/* Empty CLR Data means its a native PE executable. */
if ((rva == 0) || (rsz == 0))
{
LOG("No CLR content, not a Mono binary.");
exit(EXIT_FAILURE);
}
/*
* At this point we assume the binary is a CLR one. Technically, garbage
* files could get this far, but hopefully the Mono runtime is the best
* place to decode/explain such broken files (vs. the wine runtime).
*/
LOG("All checks passed.");
fclose(image); fclose(image);
/* Return a value indicating success or failure */ exit(EXIT_SUCCESS);
if (execType == DETECT) exit(EXIT_SUCCESS); else exit(EXIT_FAILURE);
} }

View File

@ -1,190 +0,0 @@
#ifndef __MONO_CIL_COFF_H__
#define __MONO_CIL_COFF_H__
#include <stdint.h>
/*
* 25.2.1: Method header type values
*/
#define METHOD_HEADER_FORMAT_MASK 7
#define METHOD_HEADER_TINY_FORMAT 2
#define METHOD_HEADER_TINY_FORMAT1 6
#define METHOD_HEADER_FAT_FORMAT 3
/*
* 25.2.3.1: Flags for method headers
*/
#define METHOD_HEADER_INIT_LOCALS 0x10
#define METHOD_HEADER_MORE_SECTS 0x08
/*
* For section data (25.3)
*/
#define METHOD_HEADER_SECTION_RESERVED 0
#define METHOD_HEADER_SECTION_EHTABLE 1
#define METHOD_HEADER_SECTION_OPTIL_TABLE 2
#define METHOD_HEADER_SECTION_FAT_FORMAT 0x40
#define METHOD_HEADER_SECTION_MORE_SECTS 0x80
/* 128 bytes */
typedef struct {
char msdos_sig [2];
uint16_t nlast_page;
uint16_t npages;
char msdos_header [54];
unsigned char pe_offset[4];
char msdos_header2 [64];
} MSDOSHeader;
/* 20 bytes */
typedef struct {
uint16_t coff_machine;
uint16_t coff_sections;
uint32_t coff_time;
uint32_t coff_symptr;
uint32_t coff_symcount;
uint16_t coff_opt_header_size;
uint16_t coff_attributes;
} COFFHeader;
#define COFF_ATTRIBUTE_EXECUTABLE_IMAGE 0x0002
#define COFF_ATTRIBUTE_LIBRARY_IMAGE 0x2000
/* 28 bytes */
typedef struct {
unsigned char pe_magic[2];
unsigned char pe_major;
unsigned char pe_minor;
uint32_t pe_code_size;
uint32_t pe_data_size;
uint32_t pe_uninit_data_size;
uint32_t pe_rva_entry_point;
uint32_t pe_rva_code_base;
uint32_t pe_rva_data_base;
} PEHeader;
/* 68 bytes */
typedef struct {
uint32_t pe_image_base; /* must be 0x400000 */
uint32_t pe_section_align; /* must be 8192 */
uint32_t pe_file_alignment; /* must be 512 or 4096 */
uint16_t pe_os_major; /* must be 4 */
uint16_t pe_os_minor; /* must be 0 */
uint16_t pe_user_major;
uint16_t pe_user_minor;
uint16_t pe_subsys_major;
uint16_t pe_subsys_minor;
uint32_t pe_reserved_1;
uint32_t pe_image_size;
uint32_t pe_header_size;
uint32_t pe_checksum;
uint16_t pe_subsys_required;
uint16_t pe_dll_flags;
uint32_t pe_stack_reserve;
uint32_t pe_stack_commit;
uint32_t pe_heap_reserve;
uint32_t pe_heap_commit;
uint32_t pe_loader_flags;
uint32_t pe_data_dir_count;
} PEHeaderNT;
typedef struct {
unsigned char rva[4];
uint32_t size;
} PEDirEntry;
/* 128 bytes */
typedef struct {
PEDirEntry pe_export_table;
PEDirEntry pe_import_table;
PEDirEntry pe_resource_table;
PEDirEntry pe_exception_table;
PEDirEntry pe_certificate_table;
PEDirEntry pe_reloc_table;
PEDirEntry pe_debug;
PEDirEntry pe_copyright;
PEDirEntry pe_global_ptr;
PEDirEntry pe_tls_table;
PEDirEntry pe_load_config_table;
PEDirEntry pe_bound_import;
PEDirEntry pe_iat;
PEDirEntry pe_delay_import_desc;
PEDirEntry pe_cli_header;
PEDirEntry pe_reserved;
} PEDatadir;
/* 248 bytes */
typedef struct {
char pesig [4];
COFFHeader coff;
PEHeader pe;
PEHeaderNT nt;
PEDatadir datadir;
} DotNetHeader;
typedef struct {
char st_name [8];
uint32_t st_virtual_size;
uint32_t st_virtual_address;
uint32_t st_raw_data_size;
uint32_t st_raw_data_ptr;
uint32_t st_reloc_ptr;
uint32_t st_lineno_ptr;
uint16_t st_reloc_count;
uint16_t st_line_count;
#define SECT_FLAGS_HAS_CODE 0x20
#define SECT_FLAGS_HAS_INITIALIZED_DATA 0x40
#define SECT_FLAGS_HAS_UNINITIALIZED_DATA 0x80
#define SECT_FLAGS_MEM_DISCARDABLE 0x02000000
#define SECT_FLAGS_MEM_NOT_CACHED 0x04000000
#define SECT_FLAGS_MEM_NOT_PAGED 0x08000000
#define SECT_FLAGS_MEM_SHARED 0x10000000
#define SECT_FLAGS_MEM_EXECUTE 0x20000000
#define SECT_FLAGS_MEM_READ 0x40000000
#define SECT_FLAGS_MEM_WRITE 0x80000000
uint32_t st_flags;
} SectionTable;
typedef struct {
uint32_t ch_size;
uint16_t ch_runtime_major;
uint16_t ch_runtime_minor;
PEDirEntry ch_metadata;
#define CLI_FLAGS_ILONLY 0x01
#define CLI_FLAGS_32BITREQUIRED 0x02
#define CLI_FLAGS_TRACKDEBUGDATA 0x00010000
uint32_t ch_flags;
uint32_t ch_entry_point;
PEDirEntry ch_resources;
PEDirEntry ch_strong_name;
PEDirEntry ch_code_manager_table;
PEDirEntry ch_vtable_fixups;
PEDirEntry ch_export_address_table_jumps;
/* The following are zero in the current docs */
PEDirEntry ch_eeinfo_table;
PEDirEntry ch_helper_table;
PEDirEntry ch_dynamic_info;
PEDirEntry ch_delay_load_info;
PEDirEntry ch_module_image;
PEDirEntry ch_external_fixups;
PEDirEntry ch_ridmap;
PEDirEntry ch_debug_map;
PEDirEntry ch_ip_map;
} CLIHeader;
/* This is not an on-disk structure */
typedef struct {
DotNetHeader cli_header;
int cli_section_count;
SectionTable *cli_section_tables;
void **cli_sections;
CLIHeader cli_cli_header;
} CLIImageInfo;
#endif /* __MONO_CIL_COFF_H__ */

Some files were not shown because too many files have changed in this diff Show More