#![doc(html_root_url = "https://docs.rs/xbe/0.1.1")]
#![warn(missing_debug_implementations)]
#![cfg_attr(feature = "cargo-clippy", deny(indexing_slicing))]
#![cfg_attr(feature = "cargo-clippy", allow(unreadable_literal, large_digit_groups))]
#[macro_use] extern crate bitflags;
#[macro_use] extern crate log;
#[macro_use] extern crate serde_derive;
extern crate serde;
extern crate bincode;
extern crate byteorder;
pub mod cert;
mod error;
mod kernel_symbols;
mod logo;
mod raw;
mod utils;
pub use error::Error;
pub use logo::LogoBitmap;
use cert::Certificate;
use utils::{SliceExt, NoDebug};
use byteorder::{ReadBytesExt, LE};
use std::time::{UNIX_EPOCH, SystemTime, Duration};
use std::ops::RangeInclusive;
use std::{fmt, u32};
const MAGIC_NUMBER: u32 = 0x48454258;
const ENTRY_XOR_DEBUG: u32 = 0x94859D4B;
const ENTRY_XOR_RETAIL: u32 = 0xA8FC57AB;
const THUNK_XOR_DEBUG: u32 = 0xEFB1F152;
const THUNK_XOR_RETAIL: u32 = 0x5B6D40B6;
#[derive(Debug)]
pub struct Xbe<'a> {
header: Header,
image_kind: ImageKind,
data: NoDebug<&'a [u8]>,
thunk_table: KernelThunkTable,
}
impl<'a> Xbe<'a> {
pub fn parse(data: &'a [u8]) -> Result<Self, Error> {
if data.len() >= u32::MAX as usize {
return Err(Error::Malformed(format!("image too large ({} Bytes)", data.len())));
}
let header = Header::from_raw(&raw::Header::parse(&mut &*data)?, data)?;
let mut this = Self {
thunk_table: KernelThunkTable::dummy(),
header,
image_kind: ImageKind::Retail,
data: NoDebug::from(data),
};
this.guess_image_kind()?;
Ok(this)
}
fn guess_image_kind(&mut self) -> Result<(), Error> {
let mut image_kind = None;
for &kind in &[ImageKind::Retail, ImageKind::Debug] {
let (entry_addr, thunk_addr) = (self.header.entry_point(kind), self.header.kernel_thunk_addr(kind));
let (entry_info, thunk_info) = (self.find_address_info(entry_addr), self.find_address_info(thunk_addr));
info!("{:?} entry point: {}", kind, entry_info);
info!("{:?} thunk addr: {}", kind, thunk_info);
if entry_info.section().is_some() && thunk_info.section().is_some() {
info!("both addrs good, image kind probably {:?}", kind);
image_kind = Some(kind);
break;
}
}
let image_kind = image_kind.unwrap_or_else(|| {
let fallback = ImageKind::Retail;
warn!("couldn't determine image kind, falling back to {:?}", fallback);
fallback
});
self.thunk_table = {
let virt_addr = self.header.kernel_thunk_addr(image_kind);
let raw_addr = self.header.translate_virt_addr(virt_addr)
.ok_or_else(|| Error::Malformed(format!(
"kernel thunk virt. address {:#08X} not mapped", virt_addr
)))?;
KernelThunkTable::from_raw(&*self.data, virt_addr, raw_addr)?
};
self.image_kind = image_kind;
Ok(())
}
pub fn title_name(&self) -> &str {
self.header.cert.title_name()
}
pub fn entry_point(&self) -> u32 {
self.header.entry_point(self.image_kind)
}
pub fn kernel_thunk_table(&self) -> &KernelThunkTable {
&self.thunk_table
}
pub fn sections(&self) -> Sections {
Sections::new(&self.header.section_headers, &*self.data)
}
pub fn find_section_containing(&self, virt_addr: u32) -> Option<Section> {
self.header.section_headers.iter().find(|section| {
*section.virt_range().start() <= virt_addr && *section.virt_range().end() >= virt_addr
}).map(|header| {
Section::from_xbe_and_header(&*self.data, header)
})
}
pub fn find_address_info(&self, virt_addr: u32) -> AddressInfo {
let section = self.find_section_containing(virt_addr);
let offset = if let Some(s) = §ion {
virt_addr - s.virt_range().start()
} else {
0
};
AddressInfo {
section,
offset,
address: virt_addr,
}
}
pub fn cert(&self) -> &Certificate {
&self.header.cert
}
pub fn logo(&self) -> &LogoBitmap {
&self.header.logo_bitmap
}
pub fn init_flags(&self) -> &InitFlags {
&self.header.init_flags
}
pub fn base_address(&self) -> u32 {
self.header.base_addr
}
pub fn header_size(&self) -> u32 {
self.header.header_size
}
pub fn stack_commit(&self) -> u32 {
self.header.pe_stack_commit
}
pub fn heap_reserve(&self) -> u32 {
self.header.pe_heap_reserve
}
pub fn heap_commit(&self) -> u32 {
self.header.pe_heap_commit
}
pub fn libraries(&self) -> &[LibraryVersion] {
&self.header.library_versions
}
pub fn signature(&self) -> &[u8; 256] {
&self.header.signature.0
}
pub fn debug_path(&self) -> &str {
&self.header.debug_pathname
}
pub fn debug_filename(&self) -> &str {
&self.header.debug_unicode_filename
}
pub fn raw_data(&self) -> &[u8] {
&*self.data
}
}
#[derive(Debug, Copy, Clone)]
pub enum ImageKind {
Retail,
Debug,
}
#[derive(Debug)]
struct Header {
signature: raw::Signature,
base_addr: u32,
header_size: u32,
image_size: u32,
image_header_size: u32,
time_date: SystemTime,
cert: Certificate,
section_headers: Vec<SectionHeader>,
init_flags: InitFlags,
entry_point: u32,
tls: raw::Tls,
pe_stack_commit: u32,
pe_heap_reserve: u32,
pe_heap_commit: u32,
pe_base_addr: u32,
pe_size: u32,
pe_checksum: u32,
pe_time_date: SystemTime,
debug_pathname: String,
debug_filename: String,
debug_unicode_filename: String,
kernel_thunk_addr: u32,
non_kernel_import_dir_addr: u32,
library_versions: Vec<LibraryVersion>,
kernel_library_version: Option<LibraryVersion>,
xapi_library_version: Option<LibraryVersion>,
logo_bitmap: LogoBitmap,
}
impl Header {
fn from_raw(raw: &raw::Header, data: &[u8]) -> Result<Self, Error> {
let decode_cstring = |addr: u32| -> Result<String, Error> {
if addr == 0 {
Ok(String::new())
} else {
let addr = raw.rel_addr(addr);
let string = data.try_get(addr..)?.iter()
.take_while(|b| **b != 0)
.map(|b| *b as char)
.collect::<String>();
Ok(string)
}
};
let decode_utf16 = |addr: u32| -> Result<String, Error> {
if addr == 0 {
Ok(String::new())
} else {
let addr = raw.rel_addr(addr);
let mut words = Vec::with_capacity(16);
let mut reader = data.try_get(addr..)?;
loop {
let word = reader.read_u16::<LE>()?;
if word == 0 {
break;
} else {
words.push(word);
}
}
Ok(String::from_utf16_lossy(&words))
}
};
if raw.magic != MAGIC_NUMBER {
return Err(Error::Malformed(format!(
"invalid magic number (got {:#X}, expected {:#X}", raw.magic, MAGIC_NUMBER
)));
}
Ok(Self {
signature: raw.signature,
base_addr: raw.base_addr,
header_size: raw.header_size,
image_size: raw.image_size,
image_header_size: raw.image_header_size,
time_date: UNIX_EPOCH + Duration::from_secs(raw.time_date.into()),
cert: {
let cert_addr = raw.rel_addr(raw.cert_addr);
Certificate::from_raw(&raw::Certificate::parse(&mut data.try_get(cert_addr..)?)?)?
},
section_headers: {
let mut section_headers = Vec::with_capacity(raw.num_sections as usize);
let section_header_addr = raw.rel_addr(raw.section_headers_addr);
let mut section_header_slice = data.try_get(section_header_addr..)?;
for _ in 0..raw.num_sections {
let raw_sh = raw::SectionHeader::parse(&mut section_header_slice)?;
section_headers.push(SectionHeader::from_raw(&raw, &raw_sh, data)?);
}
section_headers
},
init_flags: {
let flags = InitFlags::from_bits_truncate(raw.init_flags);
if flags.bits() != raw.init_flags {
warn!("unknown init flags: known flags: {:#X}, raw flags: {:#X}", flags.bits(), raw.init_flags);
}
flags
},
entry_point: raw.entry_point,
tls: {
let tls_addr = raw.rel_addr(raw.tls_addr);
raw::Tls::parse(&mut data.try_get(tls_addr..)?)?
},
pe_stack_commit: raw.pe_stack_commit,
pe_heap_reserve: raw.pe_heap_reserve,
pe_heap_commit: raw.pe_heap_commit,
pe_base_addr: raw.pe_base_addr,
pe_size: raw.pe_size,
pe_checksum: raw.pe_checksum,
pe_time_date: UNIX_EPOCH + Duration::from_secs(raw.pe_time_date.into()),
debug_pathname: decode_cstring(raw.debug_pathname_addr)?,
debug_filename: decode_cstring(raw.debug_filename_addr)?,
debug_unicode_filename: decode_utf16(raw.debug_unicode_filename_addr)?,
kernel_thunk_addr: raw.kernel_thunk_addr, non_kernel_import_dir_addr: raw.non_kernel_import_dir_addr,
library_versions: {
let addr = raw.rel_addr(raw.library_versions_addr);
let mut slice = data.try_get(addr..)?;
debug!("{} library version structs at {}+", raw.num_library_versions, addr);
(0..raw.num_library_versions)
.map(|_| LibraryVersion::from_raw(&raw::LibraryVersion::parse(&mut slice)?))
.collect::<Result<_, _>>()?
},
kernel_library_version: {
if raw.kernel_library_version_addr == 0 {
None
} else {
let addr = raw.rel_addr(raw.kernel_library_version_addr);
Some(LibraryVersion::from_raw(&raw::LibraryVersion::parse(&mut data.try_get(addr..)?)?)?)
}
},
xapi_library_version: {
if raw.xapi_library_version_addr == 0 {
None
} else {
let addr = raw.rel_addr(raw.xapi_library_version_addr);
Some(LibraryVersion::from_raw(&raw::LibraryVersion::parse(&mut data.try_get(addr..)?)?)?)
}
},
logo_bitmap: {
let addr = raw.rel_addr(raw.logo_bitmap_addr);
let end = addr.checked_add(raw.logo_bitmap_size)
.ok_or_else(|| Error::addr_overflow(addr, raw.logo_bitmap_size))?;
LogoBitmap::decode(data.try_get(addr..end)?)?
}
})
}
fn entry_point(&self, image_kind: ImageKind) -> u32 {
match image_kind {
ImageKind::Debug => self.entry_point ^ ENTRY_XOR_DEBUG,
ImageKind::Retail => self.entry_point ^ ENTRY_XOR_RETAIL,
}
}
fn kernel_thunk_addr(&self, image_kind: ImageKind) -> u32 {
match image_kind {
ImageKind::Debug => self.kernel_thunk_addr ^ THUNK_XOR_DEBUG,
ImageKind::Retail => self.kernel_thunk_addr ^ THUNK_XOR_RETAIL,
}
}
fn find_section_containing(&self, virt_addr: u32) -> Option<&SectionHeader> {
self.section_headers.iter().find(|section| {
*section.virt_range().start() <= virt_addr && *section.virt_range().end() >= virt_addr
})
}
fn translate_virt_addr(&self, virt_addr: u32) -> Option<u32> {
let section = self.find_section_containing(virt_addr)?;
let vstart = *section.virt_range().start();
let offset = virt_addr - vstart;
let raw = section.raw_range();
let raw_pos = raw.start().checked_add(offset)?;
if raw_pos > *raw.end() {
None } else {
Some(raw_pos)
}
}
}
bitflags! {
pub struct InitFlags: u32 {
const MOUNT_UTILITY_DRIVE = 0x00000001;
const FORMAT_UTILITY_DRIVE = 0x00000002;
const LIMIT_64_MEGABYTES = 0x00000004;
const DONT_SETUP_HARDDISK = 0x00000008;
}
}
#[derive(Debug)]
pub struct AddressInfo<'a> {
section: Option<Section<'a>>,
offset: u32,
address: u32,
}
impl<'a> AddressInfo<'a> {
pub fn virt_addr(&self) -> u32 {
self.address
}
pub fn offset(&self) -> Option<u32> {
if self.section.is_some() {
Some(self.offset)
} else {
None
}
}
pub fn section(&self) -> Option<&Section<'a>> {
self.section.as_ref()
}
}
impl<'a> fmt::Display for AddressInfo<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if let Some(section) = &self.section {
let virt_range = section.virt_range();
let raw_range = section.raw_range();
let (vstart, vend) = (virt_range.start(), virt_range.end());
let (rstart, rend) = (raw_range.start(), raw_range.end());
write!(f,
"address {:#08X} is {:#X} Bytes into section '{}' spanning virtual addresses {:#08X}..={:#08X} and raw addresses {:#08X}..={:#08X}",
self.address, self.offset, section.name(), vstart, vend, rstart, rend
)
} else {
write!(f,
"address {:#08X} is not inside any static XBE section",
self.address
)
}
}
}
#[derive(Debug)]
pub struct Sections<'a> {
headers: std::slice::Iter<'a, SectionHeader>,
image: NoDebug<&'a [u8]>,
}
impl<'a> Sections<'a> {
fn new(headers: &'a [SectionHeader], image: &'a [u8]) -> Self {
Self {
headers: headers.iter(),
image: NoDebug(image),
}
}
}
impl<'a> Iterator for Sections<'a> {
type Item = Section<'a>;
fn next(&mut self) -> Option<<Self as Iterator>::Item> {
let header = self.headers.next()?;
Some(Section::from_xbe_and_header(&*self.image, header))
}
}
#[derive(Debug)]
pub struct Section<'a> {
data: NoDebug<&'a [u8]>,
header: &'a SectionHeader,
}
impl<'a> Section<'a> {
fn from_xbe_and_header(xbe_data: &'a [u8], header: &'a SectionHeader) -> Self {
Self {
data: NoDebug(xbe_data.try_get(header.raw_range())
.expect("raw_range not in image bounds (internal error)")),
header,
}
}
pub fn virt_range(&self) -> RangeInclusive<u32> {
self.header.virt_range.clone()
}
pub fn raw_range(&self) -> RangeInclusive<u32> {
self.header.raw_range.clone()
}
pub fn name(&self) -> &'a str {
&self.header.name
}
pub fn flags(&self) -> &SectionFlags {
&self.header.section_flags
}
pub fn data(&self) -> &[u8] {
&*self.data
}
}
#[derive(Debug)]
struct SectionHeader {
section_flags: SectionFlags,
virt_range: RangeInclusive<u32>,
raw_range: RangeInclusive<u32>,
name: String,
}
impl SectionHeader {
fn from_raw(header: &raw::Header, raw: &raw::SectionHeader, data: &[u8]) -> Result<Self, Error> {
Ok(Self {
section_flags: {
let flags = SectionFlags::from_bits_truncate(raw.section_flags);
if flags.bits() != raw.section_flags {
warn!("unknown section flags: known flags: {:#X}, raw flags: {:#X}", flags.bits(), raw.section_flags);
}
flags
},
virt_range: {
raw.virt_addr.checked_add(raw.virt_size)
.ok_or_else(|| Error::addr_overflow(raw.virt_addr, raw.virt_size))?;
raw.virt_addr ..= raw.virt_addr+raw.virt_size
},
raw_range: {
raw.raw_addr.checked_add(raw.raw_size)
.ok_or_else(|| Error::addr_overflow(raw.raw_addr, raw.raw_size))?;
let range = raw.raw_addr ..= raw.raw_addr+raw.raw_size;
data.try_get(range.clone())?; range
},
name: {
let name_addr = header.rel_addr(raw.section_name_addr);
data.try_get(name_addr..)?.iter()
.take_while(|b| **b != 0)
.map(|b| *b as char)
.collect::<String>()
},
})
}
pub fn virt_range(&self) -> RangeInclusive<u32> {
self.virt_range.clone()
}
pub fn raw_range(&self) -> RangeInclusive<u32> {
self.raw_range.clone()
}
}
bitflags! {
pub struct SectionFlags: u32 {
const WRITABLE = 0x00000001;
const PRELOAD = 0x00000002;
const EXECUTABLE = 0x00000004;
const INSERTED_FILE = 0x00000008;
const HEAD_PAGE_READ_ONLY = 0x00000010;
const TAIL_PAGE_READ_ONLY = 0x00000020;
}
}
#[derive(Debug)]
pub struct LibraryVersion {
name: String,
major_version: u16,
minor_version: u16,
build_version: u16,
flags: LibraryFlags,
}
impl LibraryVersion {
fn from_raw(raw: &raw::LibraryVersion) -> Result<Self, Error> {
Ok(Self {
name: {
let name_end = raw.library_name.iter().position(|b| *b == 0).unwrap_or(0);
#[cfg_attr(feature = "cargo-clippy", allow(indexing_slicing))]
let bytes = &raw.library_name[..name_end];
String::from_utf8_lossy(bytes).to_string()
},
major_version: raw.major_version,
minor_version: raw.minor_version,
build_version: raw.build_version,
flags: {
let flags = LibraryFlags::from_bits_truncate(raw.library_flags);
if flags.bits() != raw.library_flags {
warn!("unknown library flags: known flags: {:#X}, raw flags: {:#X}", flags.bits(), raw.library_flags);
}
flags
}
})
}
pub fn flags(&self) -> &LibraryFlags {
&self.flags
}
pub fn name(&self) -> &str {
&self.name
}
pub fn version(&self) -> (u16, u16, u16) {
(self.major_version, self.minor_version, self.build_version)
}
}
bitflags! {
pub struct LibraryFlags: u16 {
const QFE_VERSION = 0x1FFF; const APPROVED = 0x6000; const DEBUG_BUILD = 0x8000;
}
}
#[derive(Debug)]
pub struct KernelThunkTable {
import_ids: Vec<ImportId>,
virt_range: RangeInclusive<u32>,
image_range: RangeInclusive<u32>,
bytes: u32,
}
impl KernelThunkTable {
fn dummy() -> Self {
Self {
import_ids: Vec::new(),
virt_range: 0..=0,
image_range: 0..=0,
bytes: 0,
}
}
fn from_raw(data: &[u8], virt_addr: u32, raw_addr: u32) -> Result<Self, Error> {
let mut table = data.try_get(raw_addr..)?;
let mut imports = Vec::new();
loop {
let import = table.read_u32::<LE>()?;
if import == 0 {
break;
}
let import = import & 0x1FF; imports.push(ImportId(import));
}
let num_imports = imports.len() as u32; Ok(Self {
import_ids: imports,
virt_range: virt_addr ..= virt_addr + num_imports * 4,
image_range: raw_addr ..= raw_addr + num_imports * 4,
bytes: num_imports * 4,
})
}
pub fn import_ids(&self) -> &[ImportId] {
&self.import_ids
}
pub fn virt_addr(&self) -> u32 {
*self.virt_range.start()
}
pub fn len(&self) -> u32 {
self.bytes
}
}
pub struct ImportId(u32);
impl ImportId {
pub fn index(&self) -> u32 {
self.0
}
pub fn name(&self) -> &str {
kernel_symbols::KERNEL_SYMBOLS.get(self.0 as usize).unwrap_or(&"<unknown import ID>")
}
}
impl fmt::Debug for ImportId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} -> {}", self.index(), self.name())
}
}