initial commit

This commit is contained in:
slonkazoid 2024-08-01 18:18:46 +03:00
commit 518ad3ba79
Signed by: slonk
SSH key fingerprint: SHA256:tbZfJX4IOvZ0LGWOWu5Ijo8jfMPi78TU7x1VoEeCIjM
9 changed files with 1584 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/target
/eventlog

1155
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

19
Cargo.toml Normal file
View file

@ -0,0 +1,19 @@
[package]
name = "calc-pcr4"
version = "0.1.0"
edition = "2021"
[dependencies]
authenticode = { version = "0.4.3", features = ["object"] }
clap = { version = "4.5.11", features = ["derive"] }
digest = "0.10.7"
fallible-iterator = "0.2.0"
hex = "0.4.3"
libc = "0.2.155"
object = "0.36.2"
sha1 = "0.10.6"
sha2 = "0.10.8"
thiserror = "1.0.63"
typed-path = "0.9.1"
uefi-eventlog = { git = "https://github.com/slonkazoid/uefi-eventlog-rs.git", rev = "a49027dc8eb7fcf09f7b797e08dbaa24d36dfc55" }
uuid = "1.10.0"

42
src/args.rs Normal file
View file

@ -0,0 +1,42 @@
use clap::{Parser, ValueEnum};
use std::path::PathBuf;
#[derive(ValueEnum, Clone, Copy)]
#[repr(u8)]
pub enum Bits {
_64,
_32,
}
#[derive(ValueEnum, Clone, Copy)]
#[repr(u8)]
pub enum Hash {
Sha1,
Sha256,
Sha512,
}
#[derive(Parser)]
pub struct Args {
#[arg(
short,
long,
help = "Address width of the EFI executables",
default_value = "64"
)]
pub bits: Bits,
#[arg(short, long, help = "Hashing algorithm", default_value = "sha256")]
pub algo: Hash,
#[arg(
short,
long,
help = "Path of the event log",
default_value = "/sys/kernel/security/tpm0/binary_bios_measurements"
)]
pub event_log: PathBuf,
#[arg(short, long, help = "Path of a JSON file with drop-ins for the files")]
pub drop_ins: Option<PathBuf>,
}

42
src/device_path.rs Normal file
View file

@ -0,0 +1,42 @@
use std::io::Read;
use uefi_eventlog::parsed::{DevicePath, DevicePathInfo, DevicePathInfoHardDriveSignatureType};
use crate::util::fixup_uuid;
fn recurse_device_path(
a: DevicePath,
mut uuid: Option<u128>,
mut path: Option<String>,
) -> (Option<u128>, Option<String>) {
match a.next {
Some(next) => {
match &next.info {
DevicePathInfo::HardDrive {
partition_signature,
signature_type,
..
} => {
if matches!(signature_type, DevicePathInfoHardDriveSignatureType::Guid) {
let mut sig = [0u8; 16];
partition_signature
.as_slice()
.read_exact(&mut sig)
.expect("expected 128-bit Guid");
uuid = Some(fixup_uuid(u128::from_be_bytes(sig)));
}
}
DevicePathInfo::FilePath { path: _path } => {
path = Some(_path.clone());
}
_ => {}
}
return recurse_device_path(*next, uuid, path);
}
None => return (uuid, path),
}
}
pub fn traverse_device_path(a: DevicePath) -> (Option<u128>, Option<String>) {
recurse_device_path(a, None, None)
}

38
src/find_mount_point.rs Normal file
View file

@ -0,0 +1,38 @@
use libc::*;
use std::ffi::{CStr, CString, OsStr};
use std::io;
use std::os::unix::ffi::OsStrExt;
use std::path::{Path, PathBuf};
pub fn by_partuuid(id: &str) -> io::Result<Option<PathBuf>> {
by_dev(PathBuf::from("/dev/disk/by-partuuid").join(id))
}
pub fn by_dev(path: impl AsRef<Path>) -> io::Result<Option<PathBuf>> {
let path = std::fs::canonicalize(path)?;
unsafe {
let path_cstring =
CString::from_vec_unchecked(Vec::from(path.as_os_str().as_encoded_bytes()));
let mounts_file = setmntent(c"/proc/mounts".as_ptr(), c"r".as_ptr());
if mounts_file.is_null() {
return Err(io::Error::from_raw_os_error(*__errno_location()));
}
loop {
let entry = getmntent(mounts_file);
if entry.is_null() {
return Err(io::Error::from_raw_os_error(*__errno_location()));
}
let entry = *entry;
let dev = CStr::from_ptr(entry.mnt_fsname);
if dev == path_cstring.as_c_str() {
let mount_point = OsStr::from_bytes(CStr::from_ptr(entry.mnt_dir).to_bytes());
return Ok(Some(PathBuf::from(mount_point)));
};
}
}
}

105
src/hash.rs Normal file
View file

@ -0,0 +1,105 @@
use std::fs::OpenOptions;
use std::io::Read;
use std::path::Path;
use digest::{Digest, Update};
use object::pe::{ImageNtHeaders32, ImageNtHeaders64};
use object::read::pe::{ImageNtHeaders, PeFile};
use crate::args::{Bits, Hash};
use crate::Error;
pub enum Hasher {
Sha1(sha1::Sha1),
Sha256(sha2::Sha256),
Sha512(sha2::Sha512),
}
impl Hasher {
pub fn finalize(self) -> Vec<u8> {
match self {
Hasher::Sha1(h) => h.finalize().into_iter().collect(),
Hasher::Sha256(h) => h.finalize().into_iter().collect(),
Hasher::Sha512(h) => h.finalize().into_iter().collect(),
}
}
pub fn output_size(&self) -> usize {
match self {
Hasher::Sha1(_) => 20,
Hasher::Sha256(_) => 32,
Hasher::Sha512(_) => 64,
}
}
}
impl Update for Hasher {
fn chain(self, data: impl AsRef<[u8]>) -> Self
where
Self: Sized,
{
match self {
Self::Sha1(h) => Self::Sha1(Update::chain(h, data)),
Self::Sha256(h) => Self::Sha256(Update::chain(h, data)),
Self::Sha512(h) => Self::Sha512(Update::chain(h, data)),
}
}
fn update(&mut self, data: &[u8]) {
match self {
Self::Sha1(h) => Update::update(h, data),
Self::Sha256(h) => Update::update(h, data),
Self::Sha512(h) => Update::update(h, data),
}
}
}
impl From<Hash> for Hasher {
fn from(value: Hash) -> Self {
match value {
Hash::Sha1 => Self::Sha1(sha1::Sha1::new()),
Hash::Sha256 => Self::Sha256(sha2::Sha256::new()),
Hash::Sha512 => Self::Sha512(sha2::Sha512::new()),
}
}
}
pub fn hash_generic<H: ImageNtHeaders>(buf: &[u8], update: &mut dyn Update) -> Result<(), Error> {
let pe: PeFile<H> = PeFile::parse(buf)?;
authenticode::authenticode_digest(&pe, update)?;
Ok(())
}
pub fn hash_by_path(
path: impl AsRef<Path>,
hasher: &mut dyn Update,
bits: Bits,
) -> Result<(), Error> {
let mut file = OpenOptions::new().read(true).open(path)?;
let mut buf = Vec::with_capacity(4096);
file.read_to_end(&mut buf)?;
match bits {
Bits::_64 => hash_generic::<ImageNtHeaders64>(&buf, hasher),
Bits::_32 => hash_generic::<ImageNtHeaders32>(&buf, hasher),
}?;
Ok(())
}
pub trait MeasureInPlace {
fn measure(&mut self, data: &[u8], hasher: Hasher);
}
impl MeasureInPlace for Vec<u8> {
fn measure(&mut self, data: &[u8], mut hasher: Hasher) {
let len = hasher.output_size();
let buf = [self.as_slice(), data].concat();
hasher.update(&buf);
self.clear();
let mut hash = hasher.finalize();
let encoded = hex::encode(hash.as_slice());
eprintln!("new hash: {encoded:0>len$}", len = len * 2);
self.append(&mut hash);
}
}

168
src/main.rs Normal file
View file

@ -0,0 +1,168 @@
#![feature(let_chains)]
mod args;
mod device_path;
mod find_mount_point;
mod hash;
mod util;
use std::fs::OpenOptions;
use std::io;
use std::path::PathBuf;
use args::Args;
use clap::Parser;
use device_path::traverse_device_path;
use fallible_iterator::FallibleIterator;
use thiserror::Error;
use typed_path::{Utf8UnixEncoding, Utf8WindowsPathBuf};
use uefi_eventlog::parsed::ParsedEventData;
use uefi_eventlog::EventType;
use crate::hash::*;
#[derive(Error, Debug)]
enum Error {
#[error("error while parsing PE file: {0}")]
PeParseError(#[from] object::Error),
#[error("error while generating authenticode hash: {0}")]
AuthenticodeError(authenticode::PeOffsetError),
#[error(transparent)]
IoError(#[from] io::Error),
}
impl From<authenticode::PeOffsetError> for Error {
fn from(value: authenticode::PeOffsetError) -> Self {
Self::AuthenticodeError(value)
}
}
fn main() {
let args = Args::parse();
let hash_len = Hasher::from(args.algo).output_size();
let mut file = OpenOptions::new().read(true).open(args.event_log).unwrap();
let fucking_settings = uefi_eventlog::ParseSettings::new();
let mut events = uefi_eventlog::Parser::new(&mut file, &fucking_settings);
let size = Hasher::from(args.algo).output_size();
let mut state = vec![0u8; size];
'process_event: loop {
let event = match events.next() {
Ok(Some(v)) => v,
Ok(None) => break,
Err(err) => {
eprintln!("error while iterating over the event log?: {err}");
continue;
}
};
if event.pcr_index == 4 {
eprintln!("processing {:?} event on pcr 4", event.event);
} else {
eprintln!(
"ignoring {:?} event on pcr {}",
event.event, event.pcr_index
);
continue;
}
'measure_file: {
match event.event {
EventType::EFIBootServicesApplication => {
let event_data = event.parsed_data.unwrap().unwrap();
if let ParsedEventData::ImageLoadEvent { device_path, .. } = event_data
&& let Some(device_path) = device_path
{
let (uuid, path) = traverse_device_path(device_path);
let windows_path_str = if let Some(windows_path_str) = path {
windows_path_str
} else {
break 'measure_file;
};
let windows_path = Utf8WindowsPathBuf::from(windows_path_str);
let unix_path = windows_path.with_encoding::<Utf8UnixEncoding>();
let esp;
'mnt: {
'get_mnt: {
let id = match uuid {
Some(id) => id,
None => {
eprintln!("uuid not in event log");
break 'get_mnt;
}
};
let maybe_point = match find_mount_point::by_partuuid(
&uuid::Uuid::from_u128(id).to_string(),
) {
Ok(maybe_point) => maybe_point,
Err(err) => {
eprintln!("error while looking up mount point: {err}");
break 'get_mnt;
}
};
match maybe_point {
Some(point) => {
esp = point;
break 'mnt;
}
None => {
eprintln!("device not mounted");
break 'get_mnt;
}
}
}
// fallback
eprintln!("couldn't find mount point, assuming \"/boot/efi\"");
esp = PathBuf::from("/boot/efi");
}
let full_path = esp.join(unix_path.strip_prefix("/").unwrap_or(&unix_path));
eprintln!("measuring file {full_path:?}");
let mut hasher = Hasher::from(args.algo);
let hash = match hash_by_path(&full_path, &mut hasher, args.bits) {
Ok(_) => {
let hash = hasher.finalize();
let encoded = hex::encode(hash.as_slice());
eprintln!("hash: {encoded:0>len$}", len = hash_len * 2);
hash
}
Err(err) => {
eprintln!("error while hashing file: {err}");
break 'measure_file;
}
};
state.measure(&hash, args.algo.into());
eprintln!("measured into state");
continue 'process_event;
}
}
_ => {}
}
}
if let Some(digest) = event.digests.first() {
let digest = digest.digest();
let encoded = hex::encode(digest.as_slice());
eprintln!("applying digest: {encoded:0>len$}", len = hash_len * 2);
state.measure(&digest, args.algo.into());
}
}
let hash = state.as_slice();
let hex = hex::encode(hash);
println!("{hex:0>len$}", len = hash_len * 2);
}

13
src/util.rs Normal file
View file

@ -0,0 +1,13 @@
pub fn fixup_uuid(data: u128) -> u128 {
let (first, second, third, fourth): (u32, u16, u16, u64) =
unsafe { std::mem::transmute(data.to_be_bytes()) };
let real = unsafe {
std::mem::transmute((
first.swap_bytes(),
second.swap_bytes(),
third.swap_bytes(),
fourth,
))
};
u128::from_be_bytes(real)
}