mirror of
https://github.com/pbatard/rufus.git
synced 2024-08-14 23:57:05 +00:00
[hash] add PE256 hash functions
* Mostly copied from U-Boot's https://github.com/u-boot/u-boot/blob/master/lib/efi_loader/efi_image_loader.c
This commit is contained in:
parent
090bfb9770
commit
c4b1b23832
5 changed files with 335 additions and 15 deletions
319
src/hash.c
319
src/hash.c
|
@ -7,6 +7,7 @@
|
|||
* Copyright © 2002-2015 Wei Dai & Igor Pavlov
|
||||
* Copyright © 2015-2023 Pete Batard <pete@akeo.ie>
|
||||
* Copyright © 2022 Jeffrey Walton <noloader@gmail.com>
|
||||
* Copyright © 2016 Alexander Graf
|
||||
*
|
||||
* 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
|
||||
|
@ -29,6 +30,8 @@
|
|||
*
|
||||
* SHA-512 modified from LibTomCrypt - Public Domain
|
||||
*
|
||||
* PE256 modified from u-boot's efi_image_loader.c - GPLv2.0+
|
||||
*
|
||||
* CPU accelerated SHA code taken from SHA-Intrinsics - Public Domain
|
||||
*
|
||||
* MD5 code from various public domain sources sharing the following
|
||||
|
@ -1469,7 +1472,7 @@ hash_init_t *hash_init[HASH_MAX] = { md5_init, sha1_init , sha256_init, sha512_i
|
|||
hash_write_t *hash_write[HASH_MAX] = { md5_write, sha1_write , sha256_write, sha512_write };
|
||||
hash_final_t *hash_final[HASH_MAX] = { md5_final, sha1_final , sha256_final, sha512_final };
|
||||
|
||||
// Compute an individual hash without threading or buffering, for a single file
|
||||
/* Compute an individual hash without threading or buffering, for a single file */
|
||||
BOOL HashFile(const unsigned type, const char* path, uint8_t* hash)
|
||||
{
|
||||
BOOL r = FALSE;
|
||||
|
@ -1511,6 +1514,318 @@ out:
|
|||
return r;
|
||||
}
|
||||
|
||||
/* A part of an image, used for hashing */
|
||||
struct image_region {
|
||||
const uint8_t* data;
|
||||
uint32_t size;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct efi_image_regions - A list of memory regions
|
||||
*
|
||||
* @max: Maximum number of regions
|
||||
* @num: Number of regions
|
||||
* @reg: Array of regions
|
||||
*/
|
||||
struct efi_image_regions {
|
||||
int max;
|
||||
int num;
|
||||
struct image_region reg[];
|
||||
};
|
||||
|
||||
/**
|
||||
* efi_image_region_add() - add an entry of region
|
||||
* @regs: Pointer to array of regions
|
||||
* @start: Start address of region (included)
|
||||
* @end: End address of region (excluded)
|
||||
* @nocheck: Flag against overlapped regions
|
||||
*
|
||||
* Take one entry of region \[@start, @end\[ and insert it into the list.
|
||||
*
|
||||
* * If @nocheck is false, the list will be sorted ascending by address.
|
||||
* Overlapping entries will not be allowed.
|
||||
*
|
||||
* * If @nocheck is true, the list will be sorted ascending by sequence
|
||||
* of adding the entries. Overlapping is allowed.
|
||||
*
|
||||
* Return: TRUE on success, FALSE on error
|
||||
*/
|
||||
BOOL efi_image_region_add(struct efi_image_regions* regs,
|
||||
const void* start, const void* end, int nocheck)
|
||||
{
|
||||
struct image_region* reg;
|
||||
int i, j;
|
||||
|
||||
if (regs->num >= regs->max) {
|
||||
uprintf("%s: no more room for regions", __func__);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (end < start)
|
||||
return FALSE;
|
||||
|
||||
for (i = 0; i < regs->num; i++) {
|
||||
reg = ®s->reg[i];
|
||||
if (nocheck)
|
||||
continue;
|
||||
|
||||
/* new data after registered region */
|
||||
if ((uint8_t*)start >= reg->data + reg->size)
|
||||
continue;
|
||||
|
||||
/* new data preceding registered region */
|
||||
if ((uint8_t*)end <= reg->data) {
|
||||
for (j = regs->num - 1; j >= i; j--)
|
||||
memcpy(®s->reg[j + 1], ®s->reg[j],
|
||||
sizeof(*reg));
|
||||
break;
|
||||
}
|
||||
|
||||
/* new data overlapping registered region */
|
||||
uprintf("%s: new region already part of another", __func__);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
reg = ®s->reg[i];
|
||||
reg->data = start;
|
||||
reg->size = (uint32_t)((uintptr_t)end - (uintptr_t)start);
|
||||
regs->num++;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* cmp_pe_section() - compare virtual addresses of two PE image sections
|
||||
* @arg1: Pointer to pointer to first section header
|
||||
* @arg2: Pointer to pointer to second section header
|
||||
*
|
||||
* Compare the virtual addresses of two sections of an portable executable.
|
||||
* The arguments are defined as const void * to allow usage with qsort().
|
||||
*
|
||||
* Return: -1 if the virtual address of arg1 is less than that of arg2,
|
||||
* 0 if the virtual addresses are equal, 1 if the virtual address
|
||||
* of arg1 is greater than that of arg2.
|
||||
*/
|
||||
static int cmp_pe_section(const void* arg1, const void* arg2)
|
||||
{
|
||||
const IMAGE_SECTION_HEADER* section1, * section2;
|
||||
|
||||
section1 = *((const IMAGE_SECTION_HEADER**)arg1);
|
||||
section2 = *((const IMAGE_SECTION_HEADER**)arg2);
|
||||
|
||||
if (section1->VirtualAddress < section2->VirtualAddress)
|
||||
return -1;
|
||||
else if (section1->VirtualAddress == section2->VirtualAddress)
|
||||
return 0;
|
||||
else
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* efi_image_parse() - parse a PE image
|
||||
* @efi: Pointer to image
|
||||
* @len: Size of @efi
|
||||
* @regp: Pointer to a list of regions
|
||||
*
|
||||
* Parse image binary in PE32(+) format, assuming that sanity of PE image
|
||||
* has been checked by a caller.
|
||||
*
|
||||
* Return: TRUE on success, FALSE on error
|
||||
*/
|
||||
BOOL efi_image_parse(uint8_t* efi, size_t len, struct efi_image_regions** regp)
|
||||
{
|
||||
struct efi_image_regions* regs;
|
||||
IMAGE_DOS_HEADER* dos;
|
||||
IMAGE_NT_HEADERS32* nt;
|
||||
IMAGE_SECTION_HEADER *sections, **sorted;
|
||||
int num_regions, num_sections, i;
|
||||
DWORD ctidx = IMAGE_DIRECTORY_ENTRY_SECURITY;
|
||||
uint32_t align, size, authsz;
|
||||
size_t bytes_hashed;
|
||||
|
||||
dos = (void*)efi;
|
||||
nt = (void*)(efi + dos->e_lfanew);
|
||||
|
||||
/*
|
||||
* Count maximum number of regions to be digested.
|
||||
* We don't have to have an exact number here.
|
||||
* See efi_image_region_add()'s in parsing below.
|
||||
*/
|
||||
num_regions = 3; /* for header */
|
||||
num_regions += nt->FileHeader.NumberOfSections;
|
||||
num_regions++; /* for extra */
|
||||
|
||||
regs = calloc(sizeof(*regs) + sizeof(struct image_region) * num_regions, 1);
|
||||
if (!regs)
|
||||
goto err;
|
||||
regs->max = num_regions;
|
||||
|
||||
/*
|
||||
* Collect data regions for hash calculation
|
||||
* 1. File headers
|
||||
*/
|
||||
if (nt->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
|
||||
IMAGE_NT_HEADERS64* nt64 = (void*)nt;
|
||||
IMAGE_OPTIONAL_HEADER64* opt = &nt64->OptionalHeader;
|
||||
|
||||
/* Skip CheckSum */
|
||||
efi_image_region_add(regs, efi, &opt->CheckSum, 0);
|
||||
if (nt64->OptionalHeader.NumberOfRvaAndSizes <= ctidx) {
|
||||
efi_image_region_add(regs,
|
||||
&opt->Subsystem,
|
||||
efi + opt->SizeOfHeaders, 0);
|
||||
} else {
|
||||
/* Skip Certificates Table */
|
||||
efi_image_region_add(regs,
|
||||
&opt->Subsystem,
|
||||
&opt->DataDirectory[ctidx], 0);
|
||||
efi_image_region_add(regs,
|
||||
&opt->DataDirectory[ctidx] + 1,
|
||||
efi + opt->SizeOfHeaders, 0);
|
||||
|
||||
authsz = opt->DataDirectory[ctidx].Size;
|
||||
}
|
||||
|
||||
bytes_hashed = opt->SizeOfHeaders;
|
||||
align = opt->FileAlignment;
|
||||
} else if (nt->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) {
|
||||
IMAGE_OPTIONAL_HEADER32* opt = &nt->OptionalHeader;
|
||||
|
||||
/* Skip CheckSum */
|
||||
efi_image_region_add(regs, efi, &opt->CheckSum, 0);
|
||||
if (nt->OptionalHeader.NumberOfRvaAndSizes <= ctidx) {
|
||||
efi_image_region_add(regs,
|
||||
&opt->Subsystem,
|
||||
efi + opt->SizeOfHeaders, 0);
|
||||
} else {
|
||||
/* Skip Certificates Table */
|
||||
efi_image_region_add(regs, &opt->Subsystem,
|
||||
&opt->DataDirectory[ctidx], 0);
|
||||
efi_image_region_add(regs,
|
||||
&opt->DataDirectory[ctidx] + 1,
|
||||
efi + opt->SizeOfHeaders, 0);
|
||||
|
||||
authsz = opt->DataDirectory[ctidx].Size;
|
||||
}
|
||||
|
||||
bytes_hashed = opt->SizeOfHeaders;
|
||||
align = opt->FileAlignment;
|
||||
} else {
|
||||
uprintf("%s: Invalid optional header magic %x", __func__,
|
||||
nt->OptionalHeader.Magic);
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* 2. Sections */
|
||||
num_sections = nt->FileHeader.NumberOfSections;
|
||||
sections = (void*)((uint8_t*)&nt->OptionalHeader +
|
||||
nt->FileHeader.SizeOfOptionalHeader);
|
||||
sorted = calloc(sizeof(IMAGE_SECTION_HEADER*), num_sections);
|
||||
if (!sorted) {
|
||||
uprintf("%s: Out of memory", __func__);
|
||||
goto err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Make sure the section list is in ascending order.
|
||||
*/
|
||||
for (i = 0; i < num_sections; i++)
|
||||
sorted[i] = §ions[i];
|
||||
qsort(sorted, num_sections, sizeof(sorted[0]), cmp_pe_section);
|
||||
|
||||
for (i = 0; i < num_sections; i++) {
|
||||
if (!sorted[i]->SizeOfRawData)
|
||||
continue;
|
||||
|
||||
size = (sorted[i]->SizeOfRawData + align - 1) & ~(align - 1);
|
||||
efi_image_region_add(regs, efi + sorted[i]->PointerToRawData,
|
||||
efi + sorted[i]->PointerToRawData + size, 0);
|
||||
//uprintf("section[%d](%s): raw: 0x%x-0x%x, virt: %x-%x",
|
||||
// i, sorted[i]->Name,
|
||||
// sorted[i]->PointerToRawData,
|
||||
// sorted[i]->PointerToRawData + size,
|
||||
// sorted[i]->VirtualAddress,
|
||||
// sorted[i]->VirtualAddress
|
||||
// + sorted[i]->Misc.VirtualSize);
|
||||
|
||||
bytes_hashed += size;
|
||||
}
|
||||
free(sorted);
|
||||
|
||||
/* 3. Extra data excluding Certificates Table */
|
||||
if (bytes_hashed + authsz < len) {
|
||||
//uprintf("extra data for hash: %zu",
|
||||
// len - (bytes_hashed + authsz));
|
||||
efi_image_region_add(regs, efi + bytes_hashed,
|
||||
efi + len - authsz, 0);
|
||||
}
|
||||
|
||||
*regp = regs;
|
||||
return TRUE;
|
||||
|
||||
err:
|
||||
free(regs);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Compute the PE256 (a.k.a. Applocker SHA256) hash of a single EFI executable.
|
||||
* This is a SHA-256 hash applied to only specific parts of a PE binary.
|
||||
* See https://security.stackexchange.com/a/199627/270178.
|
||||
* Oh, and you'd think that Windows's ImageGetDigestStream() API could be used
|
||||
* for some part of this but you'd be very, very wrong since the PE sections it
|
||||
* feeds to the hash function does include the PE header checksum field...
|
||||
*/
|
||||
BOOL PE256File(const char* path, uint8_t* hash)
|
||||
{
|
||||
BOOL r = FALSE;
|
||||
HASH_CONTEXT hash_ctx = { {0} };
|
||||
int i;
|
||||
uint32_t rb;
|
||||
uint8_t* buf = NULL;
|
||||
struct __stat64 stat64 = { 0 };
|
||||
struct efi_image_regions* regs = NULL;
|
||||
|
||||
if ((path == NULL) || (hash == NULL))
|
||||
goto out;
|
||||
|
||||
/* Filter anything that would be out of place as a EFI bootloader */
|
||||
if (_stat64U(path, &stat64) != 0) {
|
||||
uprintf("Could not open '%s", path);
|
||||
goto out;
|
||||
}
|
||||
if ((stat64.st_size < 1 * KB) || (stat64.st_size > 64 * MB)) {
|
||||
uprintf("'%s' is either too small or too large for PE-256", path);
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Read the executable into a memory buffer */
|
||||
rb = read_file(path, &buf);
|
||||
if (rb < 1 * KB)
|
||||
goto out;
|
||||
|
||||
/* Isolate the PE sections to hash */
|
||||
if (!efi_image_parse(buf, rb, ®s))
|
||||
goto out;
|
||||
|
||||
/* Hash the relevant PE data */
|
||||
sha256_init(&hash_ctx);
|
||||
for (i = 0; i < regs->num; i++)
|
||||
sha256_write(&hash_ctx, regs->reg[i].data, regs->reg[i].size);
|
||||
sha256_final(&hash_ctx);
|
||||
|
||||
memcpy(hash, hash_ctx.buf, SHA256_HASHSIZE);
|
||||
r = TRUE;
|
||||
|
||||
out:
|
||||
free(regs);
|
||||
free(buf);
|
||||
return r;
|
||||
}
|
||||
|
||||
/*
|
||||
* Compute the hash of a single buffer.
|
||||
*/
|
||||
BOOL HashBuffer(const unsigned type, const uint8_t* buf, const size_t len, uint8_t* hash)
|
||||
{
|
||||
BOOL r = FALSE;
|
||||
|
@ -1603,7 +1918,7 @@ INT_PTR CALLBACK HashCallback(HWND hDlg, UINT message, WPARAM wParam, LPARAM lPa
|
|||
return (INT_PTR)FALSE;
|
||||
}
|
||||
|
||||
// Individual thread that computes one of MD5, SHA1, SHA256 or SHA512 in parallel
|
||||
/* Individual thread that computes one of MD5, SHA1, SHA256 or SHA512 in parallel */
|
||||
DWORD WINAPI IndividualHashThread(void* param)
|
||||
{
|
||||
HASH_CONTEXT hash_ctx = { {0} }; // There's a memset in hash_init, but static analyzers still bug us
|
||||
|
|
|
@ -114,22 +114,26 @@ const char* additional_copyrights =
|
|||
"https://kolibrios.org/\\line\n"
|
||||
"GNU General Public License (GPL) v2 or later\\line\n"
|
||||
"\\line\n"
|
||||
"MD5 hash by Ron Rivest, Colin Plumb et al.\\line\n"
|
||||
"MD5 digest functions by Ron Rivest, Colin Plumb et al.\\line\n"
|
||||
"Public Domain\\line\n"
|
||||
"\\line\n"
|
||||
"SHA-1 digest functions from GnuPG:\\line\n"
|
||||
"SHA1 digest functions from GnuPG:\\line\n"
|
||||
"https://www.gnupg.org/\\line\n"
|
||||
"GNU General Public License (GPL) v3 or later\\line\n"
|
||||
"\\line\n"
|
||||
"SHA-256 digest functions 7-zip by Igor Pavlov and Crypto++ by Wei Dai:\\line\n"
|
||||
"SHA256 digest functions from 7-zip by Igor Pavlov and Crypto++ by Wei Dai:\\line\n"
|
||||
"https://7-zip.org/\\line\n"
|
||||
"https://github.com/weidai11/cryptopp/\\line\n"
|
||||
"Public Domain\\line\n"
|
||||
"\\line\n"
|
||||
"SHA-512 digest functions from libtomcrypt:\\line\n"
|
||||
"SHA512 digest functions from libtomcrypt:\\line\n"
|
||||
"https://github.com/libtom/libtomcrypt\\line\n"
|
||||
"Public Domain\\line\n"
|
||||
"\\line\n"
|
||||
"PE256 digest functions from U-Boot:\\line\n"
|
||||
"https://github.com/u-boot/u-boot\\line\n"
|
||||
"GNU General Public License (GPL) v2 or later\\line\n"
|
||||
"\\line\n"
|
||||
"About and License dialogs inspired by WinSCP by Martin Prikryl\\line\n"
|
||||
"https://winscp.net/\\line\n"
|
||||
"GNU General Public License (GPL) v3 or later\\line\n"
|
||||
|
|
|
@ -3859,10 +3859,10 @@ extern int TestHashes(void);
|
|||
// Ctrl-T => Alternate Test mode that doesn't require a full rebuild
|
||||
if ((ctrl_without_focus || ((GetKeyState(VK_CONTROL) & 0x8000) && (msg.message == WM_KEYDOWN)))
|
||||
&& (msg.wParam == 'T')) {
|
||||
// uint8_t sum[32] = { 0 };
|
||||
// PE256("D:\\Incoming\\bootx64.efi", sum);
|
||||
// DumpBufferHex(sum, 32);
|
||||
TestHashes();
|
||||
uint8_t sum[32] = { 0 };
|
||||
PE256File("C:\\Projects\\rufus\\bootx64.efi", sum);
|
||||
DumpBufferHex(sum, 32);
|
||||
// TestHashes();
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -698,6 +698,7 @@ extern HANDLE CreateFileWithTimeout(LPCSTR lpFileName, DWORD dwDesiredAccess, DW
|
|||
HANDLE hTemplateFile, DWORD dwTimeOut);
|
||||
extern BOOL SetThreadAffinity(DWORD_PTR* thread_affinity, size_t num_threads);
|
||||
extern BOOL HashFile(const unsigned type, const char* path, uint8_t* sum);
|
||||
extern BOOL PE256File(const char* path, uint8_t* hash);
|
||||
extern BOOL HashBuffer(const unsigned type, const uint8_t* buf, const size_t len, uint8_t* sum);
|
||||
extern BOOL IsFileInDB(const char* path);
|
||||
extern BOOL IsBufferInDB(const unsigned char* buf, const size_t len);
|
||||
|
|
10
src/rufus.rc
10
src/rufus.rc
|
@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
|
|||
IDD_DIALOG DIALOGEX 12, 12, 232, 326
|
||||
STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU
|
||||
EXSTYLE WS_EX_ACCEPTFILES
|
||||
CAPTION "Rufus 4.2.2048"
|
||||
CAPTION "Rufus 4.2.2049"
|
||||
FONT 9, "Segoe UI Symbol", 400, 0, 0x0
|
||||
BEGIN
|
||||
LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP
|
||||
|
@ -392,8 +392,8 @@ END
|
|||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 4,2,2048,0
|
||||
PRODUCTVERSION 4,2,2048,0
|
||||
FILEVERSION 4,2,2049,0
|
||||
PRODUCTVERSION 4,2,2049,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
|
@ -411,13 +411,13 @@ BEGIN
|
|||
VALUE "Comments", "https://rufus.ie"
|
||||
VALUE "CompanyName", "Akeo Consulting"
|
||||
VALUE "FileDescription", "Rufus"
|
||||
VALUE "FileVersion", "4.2.2048"
|
||||
VALUE "FileVersion", "4.2.2049"
|
||||
VALUE "InternalName", "Rufus"
|
||||
VALUE "LegalCopyright", "© 2011-2023 Pete Batard (GPL v3)"
|
||||
VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html"
|
||||
VALUE "OriginalFilename", "rufus-4.2.exe"
|
||||
VALUE "ProductName", "Rufus"
|
||||
VALUE "ProductVersion", "4.2.2048"
|
||||
VALUE "ProductVersion", "4.2.2049"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
|
Loading…
Reference in a new issue