-
Notifications
You must be signed in to change notification settings - Fork 183
PE: Add delay import parser #470
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
base: master
Are you sure you want to change the base?
Conversation
|
I made a brief example implementation of my idea to have I've added custom /// A binary parsing context for PE parser
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct PeCtx<'a> {
pub container: Container,
pub le: scroll::Endian,
pub sections: &'a [section_table::SectionTable],
pub file_alignment: u32,
pub opts: options::ParseOptions,
pub bytes: &'a [u8], // full binary view
}The issue is that we cannot acrually borrow the The solution to that problem is to require consumers provide impl<'a> DelayImportDll<'a> {
pub fn functions(
&self,
sections: &'a [section_table::SectionTable],
) -> DelayImportFunctionIterator<'a> {
// Replace sections with an actual sections ref
let pectx = PeCtx::new(
self.ctx.container,
self.ctx.le,
§ions,
self.ctx.file_alignment,
self.ctx.opts,
self.ctx.bytes,
);
DelayImportFunctionIterator {
ctx: pectx,
bytes: self.bytes,
offset: 0,
descriptor: self.descriptor,
}
}
}
impl<'a> DelayImportData<'a> {
pub fn dlls(&self, sections: &'a [section_table::SectionTable]) -> DelayImportDllIterator<'a> {
// Replace sections with an actual sections ref
let pectx = PeCtx::new(
self.ctx.container,
self.ctx.le,
§ions,
self.ctx.file_alignment,
self.ctx.opts,
self.ctx.bytes,
);
DelayImportDllIterator {
ctx: pectx,
bytes: self.bytes,
offset: 0,
}
}
}I think, the best is to have an offset of section table and parsing it when needed. So that we eliminate the odd /// A binary parsing context for PE parser
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct PeCtx<'a> {
pub container: Container,
pub le: scroll::Endian,
- pub sections: &'a [section_table::SectionTable],
+ // let sections = ctx.bytes.pread_with(ctx.sections_offset, scroll::LE)?;
+ pub sections_offset: usize,
pub file_alignment: u32,
pub opts: options::ParseOptions,
pub bytes: &'a [u8], // full binary view
}Otherwise it looks very clean design for me. It makes parsing of delay imports deferred to when it's needed. let res = goblin::pe::PE::parse(&buffer)?;
let di = res.delay_import_data.unwrap();
let sections = &res.sections;
let delayimports = di
.dlls(§ions)
.filter_map(Result::ok)
.flat_map(|x| x.functions(§ions))
.filter_map(Result::ok)
.collect::<Vec<_>>(); |
|
can you rebase, CI had an issue on nightly |
|
Have not fully read over the PeCtx idea, looks interesting though; few initial notes:
the case? is it some kind of cycle issue or? you can rewrite this as: let pectx = PeCtx { sections: §ions, ..self.ctx) }as long as all fields of PeCtx are accessible in the use context (e.g., public, or within the file if private, module if pub(crate)). In other languages this is called a "functional record update" or something to that effect, ocaml has a similar syntactic mechanism, for example. |
Nah it's the issue where sections is Lines 51 to 52 in f1b6cae
Line 107 in f1b6cae
|
|
sorry this didn't make it in for 0.10.1, but it does need a rebase; i did have some concerns about this, as it looked a little complicated, but i'll just have to re-review again when it's ready. Also I can't remember if it had breaking changes, so if it did it wouldn't have made it into 0.10.1; with this, I think i've caught up to your absolutely massive amount of PRs :D |
|
@m4b This PR needs a direction on which way this PR should go with: A) traditional edit: current patch includes both case. |
| } | ||
|
|
||
| #[derive(Clone, Copy, Debug)] | ||
| pub struct DelayImportFunctionIterator<'a> { |
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.
Rename to DelayImportEntryIterator since data symbols can also be delay-imported and might cause a confusion.
|
Once #239 gets merged we could add /// A binary parsing context for PE parser, including the container size, underlying byte endianness and params for resolving RVAs
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct PeCtx<'a> {
pub container: Container,
pub le: scroll::Endian,
pub section_table: &'a [u8],
pub file_alignment: u32,
pub opts: &options::ParseOptions,
pub bytes: &'a [u8], // full binary view
}impl<'a> ctx::TryFromCtx<'a, (DelayImportDescriptor, PeCtx<'a>)> for DelayImportFunction<'a> {
type Error = crate::error::Error;
fn try_from_ctx(
bytes: &'a [u8],
ctx: (DelayImportDescriptor, PeCtx<'a>),
) -> error::Result<(Self, usize)> {
let sections_it = SectionTableIterator { // Fused, ExactSize
data: ctx.section_table, // &'a [u8]
};
// Actually doesnt need to alloc here, passing iterator to utils::find_offset_ex directly or smth
let sections: Vec<SectionHeader> = sections_it.collect<Result<Vec<_>>>()?;
// Or even `ctx.section_table.pread::<&[SectionHeader]>(0)` maybe?
let name = if is_ordinal {
None
} else {
let dll_name_rva = descriptor.name_rva;
let dll_name_offset = utils::find_offset(
dll_name_rva as usize,
sections,
ctx.file_alignment,
&ctx.opts,
)
.ok_or_else(|| {
error::Error::Malformed(format!(
"cannot map delay import dll name rva {dll_name_rva:#x}"
))
})?;
let dll_name = ctx.bytes.pread::<&'a str>(dll_name_offset)?;
Some(dll_name)
};
// ...
}
} |
m4b
left a comment
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.
this LGTM, i'd like to see:
PeCtx made pub(crate); as I note, we can iterate more on the design when it's pub(crate) as we have no public api constraints/backwards compat to worry about.
other than that the other minor things, like pread::<&[u8]> (this was written before that was a common feedback I gave iirc), and some other things I note. Otherwise this should be about ready to go, thanks for your patience!
| } | ||
|
|
||
| /// Return a dubious pointer/address byte size for the container | ||
| pub fn size(self) -> usize { |
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.
this and all above should be pub(crate) for now
| ) -> error::Result<(Self, usize)> { | ||
| let mut offset = 0; | ||
|
|
||
| let (descriptor, ctx) = ctx; |
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.
you can actually do this in the function args, e.g.:
fn try_from_ctx(
bytes: &'a [u8],
(descriptor, ctx): (DelayImportDescriptor, PeCtx<'a>),| let ordinal = if is_ordinal { thunk.ordinal() } else { 0 }; | ||
| let name = if is_ordinal { | ||
| None | ||
| } else { | ||
| let dll_name_rva = descriptor.name_rva; | ||
| let dll_name_offset = utils::find_offset( | ||
| dll_name_rva as usize, | ||
| ctx.sections, | ||
| ctx.file_alignment, | ||
| &ctx.opts, | ||
| ) | ||
| .ok_or_else(|| { | ||
| error::Error::Malformed(format!( | ||
| "cannot map delay import dll name rva {dll_name_rva:#x}" | ||
| )) | ||
| })?; | ||
| let dll_name = ctx.bytes.pread::<&'a str>(dll_name_offset)?; | ||
| Some(dll_name) | ||
| }; |
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.
better to combine these two checks and return a tuple, eg.:
let (name, ordinal) = if is_ordinal { (None, thunk.ordinal()) } else {
// etc.
} | .ok_or_else(|| { | ||
| error::Error::Malformed(format!( | ||
| "cannot map delay import dll name rva {dll_name_rva:#x}" | ||
| )) | ||
| })?; |
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.
should this use the new permissive api?
| #[derive(Debug, Copy, Clone, PartialEq)] | ||
| pub struct PeCtx<'a> { | ||
| pub container: Container, | ||
| pub le: scroll::Endian, | ||
| pub sections: &'a [section_table::SectionTable], | ||
| pub file_alignment: u32, | ||
| pub opts: options::ParseOptions, | ||
| pub bytes: &'a [u8], // full binary view | ||
| } |
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.
if this is meant to be a generic Ctx for parsing PE binaries, then it shouldn't be in this module, for example. a better place is mod or even better a ctx.rs file in the src/pe directory.
| )) | ||
| })?; | ||
| let hint = bytes.pread_with::<u16>(func_name_offset, scroll::LE)?; | ||
| let name = bytes.pread::<&'a str>(func_name_offset + 2)?; // + 2 = sizeof(hint) |
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.
gread probably preferred here.
| imports.push(DelayImportEntry { | ||
| descriptor, | ||
| offset: current_name_offset as u32, | ||
| rva, | ||
| hint: 0, | ||
| dll: dll_name, | ||
| name: None, | ||
| ordinal, | ||
| }); |
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.
i prefer the fields to be constructed/returned from ordinal, which is just:
ordinal, name, and hint
you can then have a single push, e.g.:
let (ordinal, name, hint) = if is_ordiinal {
(thunk.ordinal(), None, 0)
} else {
(0, // logic in other branch)
};
imports.push(DelayImportEntry {
descriptor,
offset: current_name_offset as u32,
rva,
hint,
dll: dll_name,
name,
ordinal,,
});|
|
||
| /// Internal helper struct to simplify bitflag operations. | ||
| #[derive(Debug, SizeWith)] | ||
| struct DelayImportThunk(pub u64); |
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.
I suspect this should have been pub struct ?
| #[derive(Debug)] | ||
| pub struct DelayImportDll<'a> { | ||
| pub descriptor: DelayImportDescriptor, | ||
| ctx: PeCtx<'a>, |
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 suspect we can just put sections as a field in here and so don't have to constantly reconstruct PeCtx
|
|
||
| /// A binary parsing context for PE parser | ||
| #[derive(Debug, Copy, Clone, PartialEq)] | ||
| pub struct PeCtx<'a> { |
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.
for now let's make this pub(crate) as nothing public appears to need/use it, this way we can iterate on the design however we like, and it can unblock getting this nice PR in.
Added delay-load import directory parser for PE32/64.
Using
Vecas well as existing normal import parser. The design decision behind thatVecis that each import entry requires RVA resolution. I'd say it is not a good design, as both normal and delay import parser runs fully upongoblin::pe::PE::parsethat can raise malformed errors -- even if the consumer doesn't need that import info.I guess almost entire parsing logic can be moved to
TryFromCtxif we could build some specific scroll ctx with a hell of lifetimes, so thatutils::find_offsetbecame callable inside theTryFromCtx. That's too much hustle but it should theoretically be possible.Ref: