-
Notifications
You must be signed in to change notification settings - Fork 163
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
PE: parse thread local storage - TLS data (#404)
* PE: parse thread local storage - TLS data * Breaking change
- Loading branch information
1 parent
eda1be7
commit 6c5b3e8
Showing
2 changed files
with
282 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,254 @@ | ||
use crate::error; | ||
use alloc::vec::Vec; | ||
use scroll::{Pread, Pwrite, SizeWith}; | ||
|
||
use crate::pe::data_directories; | ||
use crate::pe::options; | ||
use crate::pe::section_table; | ||
use crate::pe::utils; | ||
|
||
/// Represents the TLS directory `IMAGE_TLS_DIRECTORY64`. | ||
#[repr(C)] | ||
#[derive(Debug, PartialEq, Copy, Clone, Default, Pread, Pwrite, SizeWith)] | ||
pub struct ImageTlsDirectory { | ||
/// The starting address of the TLS raw data. | ||
// NOTE: `u32` for 32-bit binaries, `u64` for 64-bit binaries. | ||
pub start_address_of_raw_data: u64, | ||
/// The ending address of the TLS raw data. | ||
// NOTE: `u32` for 32-bit binaries, `u64` for 64-bit binaries. | ||
pub end_address_of_raw_data: u64, | ||
/// The address of the TLS index. | ||
// NOTE: `u32` for 32-bit binaries, `u64` for 64-bit binaries. | ||
pub address_of_index: u64, | ||
/// The address of the TLS callback functions. | ||
/// | ||
/// Terminated by a null pointer. | ||
// NOTE: `u32` for 32-bit binaries, `u64` for 64-bit binaries. | ||
pub address_of_callbacks: u64, | ||
/// The size of the zero fill. | ||
pub size_of_zero_fill: u32, | ||
/// The characteristics of the TLS. | ||
pub characteristics: u32, | ||
} | ||
|
||
/// TLS information. | ||
#[derive(Debug, Clone, PartialEq, Default)] | ||
pub struct TlsData<'a> { | ||
/// TLS directory. | ||
pub image_tls_directory: ImageTlsDirectory, | ||
/// Raw data of the TLS. | ||
pub raw_data: Option<&'a [u8]>, | ||
/// TLS index. | ||
pub slot: Option<u32>, | ||
/// TLS callbacks. | ||
pub callbacks: Vec<u64>, | ||
} | ||
|
||
impl ImageTlsDirectory { | ||
pub fn parse<T: Sized>( | ||
bytes: &[u8], | ||
dd: data_directories::DataDirectory, | ||
sections: &[section_table::SectionTable], | ||
file_alignment: u32, | ||
) -> error::Result<Self> { | ||
Self::parse_with_opts::<T>( | ||
bytes, | ||
dd, | ||
sections, | ||
file_alignment, | ||
&options::ParseOptions::default(), | ||
) | ||
} | ||
|
||
pub fn parse_with_opts<T: Sized>( | ||
bytes: &[u8], | ||
dd: data_directories::DataDirectory, | ||
sections: &[section_table::SectionTable], | ||
file_alignment: u32, | ||
opts: &options::ParseOptions, | ||
) -> error::Result<Self> { | ||
let rva = dd.virtual_address as usize; | ||
let mut offset = | ||
utils::find_offset(rva, sections, file_alignment, opts).ok_or_else(|| { | ||
error::Error::Malformed(format!( | ||
"Cannot map ImageTlsDirectory rva {:#x} into offset", | ||
rva | ||
)) | ||
})?; | ||
|
||
let is_64 = core::mem::size_of::<T>() == 8; | ||
|
||
let start_address_of_raw_data = if is_64 { | ||
bytes.gread_with::<u64>(&mut offset, scroll::LE)? | ||
} else { | ||
bytes.gread_with::<u32>(&mut offset, scroll::LE)? as u64 | ||
}; | ||
let end_address_of_raw_data = if is_64 { | ||
bytes.gread_with::<u64>(&mut offset, scroll::LE)? | ||
} else { | ||
bytes.gread_with::<u32>(&mut offset, scroll::LE)? as u64 | ||
}; | ||
let address_of_index = if is_64 { | ||
bytes.gread_with::<u64>(&mut offset, scroll::LE)? | ||
} else { | ||
bytes.gread_with::<u32>(&mut offset, scroll::LE)? as u64 | ||
}; | ||
let address_of_callbacks = if is_64 { | ||
bytes.gread_with::<u64>(&mut offset, scroll::LE)? | ||
} else { | ||
bytes.gread_with::<u32>(&mut offset, scroll::LE)? as u64 | ||
}; | ||
let size_of_zero_fill = bytes.gread_with::<u32>(&mut offset, scroll::LE)?; | ||
let characteristics = bytes.gread_with::<u32>(&mut offset, scroll::LE)?; | ||
|
||
let itd = Self { | ||
start_address_of_raw_data, | ||
end_address_of_raw_data, | ||
address_of_index, | ||
address_of_callbacks, | ||
size_of_zero_fill, | ||
characteristics, | ||
}; | ||
|
||
Ok(itd) | ||
} | ||
} | ||
|
||
impl<'a> TlsData<'a> { | ||
pub fn parse<T: Sized>( | ||
bytes: &'a [u8], | ||
image_base: usize, | ||
dd: &data_directories::DataDirectory, | ||
sections: &[section_table::SectionTable], | ||
file_alignment: u32, | ||
) -> error::Result<Option<Self>> { | ||
Self::parse_with_opts::<T>( | ||
bytes, | ||
image_base, | ||
dd, | ||
sections, | ||
file_alignment, | ||
&options::ParseOptions::default(), | ||
) | ||
} | ||
|
||
pub fn parse_with_opts<T: Sized>( | ||
bytes: &'a [u8], | ||
image_base: usize, | ||
dd: &data_directories::DataDirectory, | ||
sections: &[section_table::SectionTable], | ||
file_alignment: u32, | ||
opts: &options::ParseOptions, | ||
) -> error::Result<Option<Self>> { | ||
let mut raw_data = None; | ||
let mut slot = None; | ||
let mut callbacks = Vec::new(); | ||
|
||
let is_64 = core::mem::size_of::<T>() == 8; | ||
|
||
let itd = | ||
ImageTlsDirectory::parse_with_opts::<T>(bytes, *dd, sections, file_alignment, opts)?; | ||
|
||
// Parse the raw data if any | ||
if itd.end_address_of_raw_data != 0 && itd.start_address_of_raw_data != 0 { | ||
if itd.start_address_of_raw_data > itd.end_address_of_raw_data { | ||
return Err(error::Error::Malformed(format!( | ||
"tls start_address_of_raw_data ({:#x}) is greater than end_address_of_raw_data ({:#x})", | ||
itd.start_address_of_raw_data, | ||
itd.end_address_of_raw_data | ||
))); | ||
} | ||
|
||
if (itd.start_address_of_raw_data as usize) < image_base { | ||
return Err(error::Error::Malformed(format!( | ||
"tls start_address_of_raw_data ({:#x}) is less than image base ({:#x})", | ||
itd.start_address_of_raw_data, image_base | ||
))); | ||
} | ||
|
||
// VA to RVA | ||
let rva = itd.start_address_of_raw_data as usize - image_base; | ||
let size = itd.end_address_of_raw_data - itd.start_address_of_raw_data; | ||
let offset = | ||
utils::find_offset(rva, sections, file_alignment, opts).ok_or_else(|| { | ||
error::Error::Malformed(format!( | ||
"cannot map tls start_address_of_raw_data rva ({:#x}) into offset", | ||
rva | ||
)) | ||
})?; | ||
raw_data = Some(&bytes[offset..offset + size as usize]); | ||
} | ||
|
||
// Parse the index if any | ||
if itd.address_of_index != 0 { | ||
if (itd.address_of_index as usize) < image_base { | ||
return Err(error::Error::Malformed(format!( | ||
"tls address_of_index ({:#x}) is less than image base ({:#x})", | ||
itd.address_of_index, image_base | ||
))); | ||
} | ||
|
||
// VA to RVA | ||
let rva = itd.address_of_index as usize - image_base; | ||
let offset = | ||
utils::find_offset(rva, sections, file_alignment, opts).ok_or_else(|| { | ||
error::Error::Malformed(format!( | ||
"cannot map tls address_of_index rva ({:#x}) into offset", | ||
rva | ||
)) | ||
})?; | ||
|
||
slot = Some(bytes.pread_with::<u32>(offset, scroll::LE)?); | ||
} | ||
|
||
// Parse the callbacks if any | ||
if itd.address_of_callbacks != 0 { | ||
if (itd.address_of_callbacks as usize) < image_base { | ||
return Err(error::Error::Malformed(format!( | ||
"tls address_of_callbacks ({:#x}) is less than image base ({:#x})", | ||
itd.address_of_callbacks, image_base | ||
))); | ||
} | ||
|
||
// VA to RVA | ||
let rva = itd.address_of_callbacks as usize - image_base; | ||
let offset = | ||
utils::find_offset(rva, sections, file_alignment, opts).ok_or_else(|| { | ||
error::Error::Malformed(format!( | ||
"cannot map tls address_of_callbacks rva ({:#x}) into offset", | ||
rva | ||
)) | ||
})?; | ||
let mut i = 0; | ||
// Read the callbacks until we find a null terminator | ||
loop { | ||
let callback: u64 = if is_64 { | ||
bytes.pread_with::<u64>(offset + i * 8, scroll::LE)? | ||
} else { | ||
bytes.pread_with::<u32>(offset + i * 4, scroll::LE)? as u64 | ||
}; | ||
// Each callback is an VA so convert it to RVA | ||
let callback_rva = callback as usize - image_base; | ||
// Check if the callback is in the image | ||
if utils::find_offset(callback_rva, sections, file_alignment, opts).is_none() { | ||
return Err(error::Error::Malformed(format!( | ||
"cannot map tls callback ({:#x})", | ||
callback | ||
))); | ||
} | ||
if callback == 0 { | ||
break; | ||
} | ||
callbacks.push(callback); | ||
i += 1; | ||
} | ||
} | ||
|
||
Ok(Some(TlsData { | ||
image_tls_directory: itd, | ||
raw_data, | ||
slot, | ||
callbacks, | ||
})) | ||
} | ||
} |