-
Notifications
You must be signed in to change notification settings - Fork 163
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
PE: parse thread local storage - TLS data #404
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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, | ||
})) | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
on a malformed binary could this cause an infinite loop?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. I think it would make sense to have a RVA validation there and return the malformed error if invalid RVAs found.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so what i meant specifically is: is this loop guaranteed to terminate? this comes up when there are malformed binaries, and the loop only terminates in a well-formed case, but if the loop has no upper bound, it might cause the library to run forever, which is unacceptable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The RVA check I previously introduced should be sufficient to address the malformations. In general, it does not result in an infinite loop. However, there might be instances where it could raise slice bound errors. Typically, the TLS callback array is located within the data sections—a common characteristic across multiple PECOFF compilers—and the data section is well-aligned with zeros. In my experience handling over 10,000 to 20,000 unique PECOFF binaries, I have not encountered any infinite loops with this implementation. Nonetheless, I believe setting a reasonable limit would be prudent. Is this the solution you were seeking?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea I was just making sure there wasn't some obvious termination condition we could add, e.g., end of file, etc., that would guarantee we are done looping; my general reflex when i see
loop
is to ask how it can terminate, as we've had bugs in past of infinite loops without proper termination condition, but your answer is sufficient, thank you!