Skip to content

Commit 82d8538

Browse files
author
Alan Jowett
committed
Add ELF file hash to PE image
Signed-off-by: Alan Jowett <alan.jowett@microsoft.com>
1 parent 6e47294 commit 82d8538

File tree

15 files changed

+520
-20
lines changed

15 files changed

+520
-20
lines changed

ebpfapi/Source.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ EXPORTS
105105
ebpf_api_elf_verify_program_from_file
106106
ebpf_api_elf_verify_program_from_memory
107107
ebpf_api_close_handle
108+
ebpf_api_get_data_section
108109
ebpf_api_get_pinned_map_info
109110
ebpf_api_map_info_free
110111
ebpf_canonicalize_pin_path

include/ebpf_api.h

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -67,14 +67,14 @@ extern "C"
6767
} ebpf_api_program_info_t;
6868

6969
/**
70-
* @brief Get list of programs and stats in an eBPF file.
71-
* @param[in] file Name of file containing eBPF programs.
72-
* @param[in] verbose Obtain additional info about the programs.
73-
* @param[out] infos On success points to a list of eBPF programs.
74-
* The caller is responsible for freeing the list via ebpf_free_programs().
75-
* @param[out] error_message On failure points to a text description of
76-
* the error.
77-
*/
70+
* @brief Get list of programs and stats in an eBPF file.
71+
* @param[in] file Name of file containing eBPF programs.
72+
* @param[in] verbose Obtain additional info about the programs.
73+
* @param[out] infos On success points to a list of eBPF programs.
74+
* The caller is responsible for freeing the list via ebpf_free_programs().
75+
* @param[out] error_message On failure points to a text description of
76+
* the error.
77+
*/
7878
_Must_inspect_result_ ebpf_result_t
7979
ebpf_enumerate_programs(
8080
_In_z_ const char* file,
@@ -798,6 +798,25 @@ extern "C"
798798
_In_opt_ void* ctx,
799799
_In_opt_ const struct ebpf_perf_buffer_opts* opts) EBPF_NO_EXCEPT;
800800

801+
/**
802+
* @brief Extract data from a named section in a PE or ELF file.
803+
* @param[in] file_path Path to the PE or ELF file.
804+
* @param[in] section_name Name of the section to extract.
805+
* @param[out] data Pointer to buffer to receive section data. If NULL, only the size is returned.
806+
* @param[in,out] data_size On input, size of the buffer. On output, actual size of section data.
807+
* @retval EBPF_SUCCESS The operation was successful.
808+
* @retval EBPF_INSUFFICIENT_BUFFER The buffer is too small. data_size contains required size.
809+
* @retval EBPF_INVALID_ARGUMENT Invalid parameters.
810+
* @retval EBPF_OBJECT_NOT_FOUND Section not found in file.
811+
* @retval EBPF_INVALID_OBJECT File format is invalid or unsupported.
812+
*/
813+
_Must_inspect_result_ ebpf_result_t
814+
ebpf_api_get_data_section(
815+
_In_z_ const char* file_path,
816+
_In_z_ const char* section_name,
817+
_Out_writes_bytes_opt_(*data_size) uint8_t* data,
818+
_Inout_ size_t* data_size) EBPF_NO_EXCEPT;
819+
801820
#ifdef __cplusplus
802821
}
803822
#endif

libs/api/ebpf_api.cpp

Lines changed: 135 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
#include "ebpf_serialize.h"
1616
#include "ebpf_shared_framework.h"
1717
#include "ebpf_tracelog.h"
18+
// Undefine Windows min/max macros for ELFIO
19+
#undef max
20+
#undef min
21+
#include "elfio_wrapper.hpp"
1822
#include "hash.h"
1923
#pragma warning(push)
2024
#pragma warning(disable : 4200) // Zero-sized array in struct/union
@@ -564,7 +568,7 @@ _ebpf_map_lookup_element_batch_helper(
564568

565569
while (count_returned < input_count) {
566570
// Fetch the next batch of entries.
567-
size_t entries_to_fetch = min(input_count - count_returned, max_entries_per_batch);
571+
size_t entries_to_fetch = std::min(input_count - count_returned, max_entries_per_batch);
568572

569573
ebpf_protocol_buffer_t request_buffer(
570574
EBPF_OFFSET_OF(_ebpf_operation_map_get_next_key_value_batch_request, previous_key) +
@@ -772,7 +776,7 @@ _update_map_element_batch(
772776
try {
773777
for (size_t key_index = 0; key_index < input_count;) {
774778
// Compute the number of entries to update in this batch.
775-
size_t entries_to_update = min(input_count - key_index, max_entries_per_batch);
779+
size_t entries_to_update = std::min(input_count - key_index, max_entries_per_batch);
776780

777781
request_buffer.resize(
778782
EBPF_OFFSET_OF(ebpf_operation_map_update_element_batch_request_t, data) +
@@ -1102,7 +1106,7 @@ ebpf_map_delete_element_batch(fd_t map_fd, _In_ const void* keys, _Inout_ uint32
11021106
try {
11031107
for (size_t key_index = 0; key_index < input_count;) {
11041108
// Compute the number of entries to update in this batch.
1105-
size_t entries_to_delete = min(input_count - key_index, max_entries_per_batch);
1109+
size_t entries_to_delete = std::min(input_count - key_index, max_entries_per_batch);
11061110

11071111
request_buffer.resize(
11081112
EBPF_OFFSET_OF(ebpf_operation_map_delete_element_batch_request_t, keys) + key_size * entries_to_delete);
@@ -5252,6 +5256,134 @@ ebpf_map_set_wait_handle(fd_t map_fd, uint64_t index, ebpf_handle_t handle) NO_E
52525256
}
52535257
CATCH_NO_MEMORY_EBPF_RESULT
52545258

5259+
// Context structure for section data extraction
5260+
typedef struct _ebpf_section_data_context
5261+
{
5262+
const char* section_name;
5263+
bool section_found;
5264+
size_t section_size;
5265+
const uint8_t* section_data;
5266+
} ebpf_section_data_context_t;
5267+
5268+
// Callback function for PE section iteration
5269+
static int
5270+
_ebpf_pe_find_section(
5271+
_Inout_ void* context,
5272+
_In_ const VA& va,
5273+
_In_ const std::string& section_name,
5274+
_In_ const image_section_header& section_header,
5275+
_In_ const bounded_buffer* buffer) NO_EXCEPT_TRY
5276+
{
5277+
UNREFERENCED_PARAMETER(va);
5278+
UNREFERENCED_PARAMETER(section_header);
5279+
5280+
ebpf_section_data_context_t* ctx = static_cast<ebpf_section_data_context_t*>(context);
5281+
5282+
if (section_name == ctx->section_name && buffer != nullptr) {
5283+
ctx->section_found = true;
5284+
ctx->section_size = buffer->bufLen;
5285+
ctx->section_data = buffer->buf;
5286+
return 1; // Stop iteration
5287+
}
5288+
return 0; // Continue iteration
5289+
}
5290+
CATCH_NO_MEMORY_INT(1)
5291+
5292+
_Must_inspect_result_ ebpf_result_t
5293+
ebpf_api_get_data_section(
5294+
_In_z_ const char* file_path,
5295+
_In_z_ const char* section_name,
5296+
_Out_writes_bytes_opt_(*data_size) uint8_t* data,
5297+
_Inout_ size_t* data_size) NO_EXCEPT_TRY
5298+
{
5299+
EBPF_LOG_ENTRY();
5300+
5301+
if (file_path == nullptr || section_name == nullptr || data_size == nullptr) {
5302+
EBPF_RETURN_RESULT(EBPF_INVALID_ARGUMENT);
5303+
}
5304+
5305+
// Determine file type by extension
5306+
std::string path(file_path);
5307+
bool is_pe_file = false;
5308+
if (path.size() > 4) {
5309+
std::string extension = path.substr(path.size() - 4);
5310+
std::transform(extension.begin(), extension.end(), extension.begin(), [](char c) {
5311+
return static_cast<char>(::tolower(static_cast<unsigned char>(c)));
5312+
});
5313+
is_pe_file = (extension == ".dll" || extension == ".sys" || extension == ".exe");
5314+
}
5315+
5316+
ebpf_result_t result = EBPF_SUCCESS;
5317+
5318+
if (is_pe_file) {
5319+
// Parse PE file using pe-parse library
5320+
std::scoped_lock pe_parse_lock(_pe_parse_mutex);
5321+
parsed_pe* pe = ParsePEFromFile(file_path);
5322+
if (pe == nullptr) {
5323+
EBPF_RETURN_RESULT(EBPF_INVALID_OBJECT);
5324+
}
5325+
5326+
// Set up context for section search
5327+
ebpf_section_data_context_t section_context = {
5328+
.section_name = section_name, .section_found = false, .section_size = 0, .section_data = nullptr};
5329+
5330+
IterSec(pe, _ebpf_pe_find_section, &section_context);
5331+
5332+
if (!section_context.section_found) {
5333+
DestructParsedPE(pe);
5334+
EBPF_RETURN_RESULT(EBPF_OBJECT_NOT_FOUND);
5335+
}
5336+
5337+
// Check buffer size
5338+
if (data == nullptr) {
5339+
// Just return the size
5340+
*data_size = section_context.section_size;
5341+
} else if (*data_size < section_context.section_size) {
5342+
// Buffer too small
5343+
*data_size = section_context.section_size;
5344+
DestructParsedPE(pe);
5345+
EBPF_RETURN_RESULT(EBPF_INSUFFICIENT_BUFFER);
5346+
} else {
5347+
// Copy data to buffer
5348+
memcpy(data, section_context.section_data, section_context.section_size);
5349+
*data_size = section_context.section_size;
5350+
}
5351+
5352+
DestructParsedPE(pe);
5353+
} else {
5354+
// Parse ELF file using ELFIO library
5355+
ELFIO::elfio reader;
5356+
if (!reader.load(file_path)) {
5357+
EBPF_RETURN_RESULT(EBPF_INVALID_OBJECT);
5358+
}
5359+
5360+
// Find the section by name
5361+
ELFIO::section* section = reader.sections[section_name];
5362+
if (section == nullptr || section->get_data() == nullptr) {
5363+
EBPF_RETURN_RESULT(EBPF_OBJECT_NOT_FOUND);
5364+
}
5365+
5366+
size_t section_size = section->get_size();
5367+
5368+
// Check buffer size
5369+
if (data == nullptr) {
5370+
// Just return the size
5371+
*data_size = section_size;
5372+
} else if (*data_size < section_size) {
5373+
// Buffer too small
5374+
*data_size = section_size;
5375+
EBPF_RETURN_RESULT(EBPF_INSUFFICIENT_BUFFER);
5376+
} else {
5377+
// Copy data to buffer
5378+
memcpy(data, section->get_data(), section_size);
5379+
*data_size = section_size;
5380+
}
5381+
}
5382+
5383+
EBPF_RETURN_RESULT(result);
5384+
}
5385+
CATCH_NO_MEMORY_EBPF_RESULT
5386+
52555387
static _Must_inspect_result_ ebpf_result_t
52565388
_ebpf_link_mark_as_legacy_mode(ebpf_handle_t link_handle) NO_EXCEPT_TRY
52575389
{

libs/ebpfnetsh/ebpfnetsh.vcxproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@
101101
</ItemDefinitionGroup>
102102
<ItemGroup>
103103
<ClCompile Include="elf.cpp" />
104+
<ClCompile Include="netsh_hash.cpp" />
104105
<ClCompile Include="links.cpp" />
105106
<ClCompile Include="maps.cpp" />
106107
<ClCompile Include="pins.cpp" />
@@ -110,6 +111,7 @@
110111
</ItemGroup>
111112
<ItemGroup>
112113
<ClInclude Include="elf.h" />
114+
<ClInclude Include="netsh_hash.h" />
113115
<ClInclude Include="links.h" />
114116
<ClInclude Include="maps.h" />
115117
<ClInclude Include="pins.h" />

libs/ebpfnetsh/ebpfnetsh.vcxproj.filters

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
<ClCompile Include="elf.cpp">
2323
<Filter>Source Files</Filter>
2424
</ClCompile>
25+
<ClCompile Include="netsh_hash.cpp">
26+
<Filter>Source Files</Filter>
27+
</ClCompile>
2528
<ClCompile Include="programs.cpp">
2629
<Filter>Source Files</Filter>
2730
</ClCompile>
@@ -45,6 +48,9 @@
4548
<ClInclude Include="elf.h">
4649
<Filter>Header Files</Filter>
4750
</ClInclude>
51+
<ClInclude Include="netsh_hash.h">
52+
<Filter>Header Files</Filter>
53+
</ClInclude>
4854
<ClInclude Include="programs.h">
4955
<Filter>Header Files</Filter>
5056
</ClInclude>

libs/ebpfnetsh/netsh_hash.cpp

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
// Copyright (c) eBPF for Windows contributors
2+
// SPDX-License-Identifier: MIT
3+
4+
#include "ebpf_api.h"
5+
#include "netsh_hash.h"
6+
#include "platform.h"
7+
#include "tokens.h"
8+
#include "utilities.h"
9+
10+
#include <iomanip>
11+
#include <iostream>
12+
#include <string>
13+
#include <vector>
14+
15+
// The following function uses windows specific type as an input to match
16+
// definition of "FN_HANDLE_CMD" in public file of NetSh.h
17+
unsigned long
18+
handle_ebpf_show_hash(
19+
IN LPCWSTR machine,
20+
_Inout_updates_(argc) LPWSTR* argv,
21+
IN DWORD current_index,
22+
IN DWORD argc,
23+
IN DWORD flags,
24+
IN LPCVOID data,
25+
OUT BOOL* done)
26+
{
27+
UNREFERENCED_PARAMETER(machine);
28+
UNREFERENCED_PARAMETER(flags);
29+
UNREFERENCED_PARAMETER(data);
30+
UNREFERENCED_PARAMETER(done);
31+
32+
TAG_TYPE tags[] = {
33+
{TOKEN_FILENAME, NS_REQ_PRESENT, FALSE},
34+
{TOKEN_HASHONLY, NS_REQ_ZERO, FALSE},
35+
};
36+
const int FILENAME_INDEX = 0;
37+
const int HASHONLY_INDEX = 1;
38+
39+
unsigned long tag_type[_countof(tags)] = {0};
40+
41+
unsigned long status =
42+
PreprocessCommand(nullptr, argv, current_index, argc, tags, _countof(tags), 0, _countof(tags), tag_type);
43+
44+
std::string filename;
45+
bool hash_only = false;
46+
for (int i = 0; (status == NO_ERROR) && ((i + current_index) < argc); i++) {
47+
switch (tag_type[i]) {
48+
case FILENAME_INDEX: {
49+
filename = down_cast_from_wstring(std::wstring(argv[current_index + i]));
50+
break;
51+
}
52+
case HASHONLY_INDEX: {
53+
hash_only = true;
54+
break;
55+
}
56+
default:
57+
status = ERROR_INVALID_SYNTAX;
58+
break;
59+
}
60+
}
61+
62+
if (status != NO_ERROR) {
63+
return status;
64+
}
65+
66+
// First get the size of the hash section
67+
size_t hash_size = 0;
68+
ebpf_result_t result = ebpf_api_get_data_section(filename.c_str(), "hash", nullptr, &hash_size);
69+
70+
if (result == EBPF_INVALID_OBJECT) {
71+
std::cout << "error: No such file or directory opening " << filename << std::endl;
72+
return ERROR_SUPPRESS_OUTPUT;
73+
} else if (result == EBPF_OBJECT_NOT_FOUND) {
74+
std::cout << "No hash section found in " << filename << std::endl;
75+
return ERROR_SUPPRESS_OUTPUT;
76+
} else if (result != EBPF_SUCCESS) {
77+
std::cout << "Error reading hash from " << filename << ": " << result << std::endl;
78+
return ERROR_SUPPRESS_OUTPUT;
79+
}
80+
81+
if (hash_size == 0) {
82+
std::cout << "Hash section is empty in " << filename << std::endl;
83+
return ERROR_SUPPRESS_OUTPUT;
84+
}
85+
86+
// Allocate buffer and get the hash data
87+
std::vector<uint8_t> hash_data(hash_size);
88+
result = ebpf_api_get_data_section(filename.c_str(), "hash", hash_data.data(), &hash_size);
89+
90+
if (result != EBPF_SUCCESS) {
91+
std::cout << "Error reading hash data from " << filename << ": " << result << std::endl;
92+
return ERROR_SUPPRESS_OUTPUT;
93+
}
94+
95+
// Truncate hash to size of a SHA-256 hash if larger
96+
if (hash_size > 32) {
97+
hash_size = 32;
98+
}
99+
100+
// Resize vector to actual hash size
101+
hash_data.resize(hash_size);
102+
103+
if (hash_only) {
104+
// Print hash in PowerShell Get-FileHash format (uppercase, no spaces)
105+
for (size_t i = 0; i < hash_size; i++) {
106+
std::cout << std::hex << std::uppercase << std::setw(2) << std::setfill('0')
107+
<< static_cast<unsigned int>(hash_data[i]);
108+
}
109+
std::cout << std::dec << std::nouppercase << std::endl; // Reset formatting
110+
} else {
111+
// Print detailed hash information
112+
std::cout << "Hash for " << filename << ":" << std::endl;
113+
std::cout << "Size: " << hash_size << " bytes" << std::endl;
114+
std::cout << "Data: ";
115+
116+
// Print hash in hexadecimal format with spaces
117+
for (size_t i = 0; i < hash_size; i++) {
118+
if (i > 0 && i % 16 == 0) {
119+
std::cout << std::endl << " ";
120+
}
121+
std::cout << std::hex << std::setw(2) << std::setfill('0') << static_cast<unsigned int>(hash_data[i]);
122+
if (i < hash_size - 1) {
123+
std::cout << " ";
124+
}
125+
}
126+
std::cout << std::dec << std::endl; // Reset to decimal format
127+
}
128+
129+
return NO_ERROR;
130+
}

0 commit comments

Comments
 (0)