linux-packaging-mono/debian/detector/binfmt-detector-cli.c

182 lines
5.0 KiB
C
Raw Permalink Normal View History

/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Library General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* 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>
*/
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#define LOG_ENABLED 0
#define LOG(fmt, ...) \
if (LOG_ENABLED) { \
fprintf(stderr, fmt "\n" \
__VA_OPT__(,) __VA_ARGS__ ); \
}
/* 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;
}
int main(int argc, char **argv)
{
if (argc < 2) exit(EXIT_FAILURE);
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)
{
LOG("Wrong PE_signature (got %#08x)", pe_signature);
exit(EXIT_FAILURE);
}
/*
* 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);
exit(EXIT_SUCCESS);
}