Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 85 additions & 25 deletions src/addr_info.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use crate::bytes_info::BytesInfo;
use crate::id0::entry_iter::EntryTagContinuousSubkeys;
use crate::id0::flag::nalt::x::NALT_DREF_FROM;
use crate::id0::flag::nalt::{NALT_ENUM0, NALT_ENUM1, NALT_STRTYPE};
use crate::id0::flag::netnode::nn_res::{ARRAY_ALT_TAG, ARRAY_SUP_TAG};
Expand Down Expand Up @@ -242,37 +241,38 @@ impl<'a, K: IDAKind> AddressInfo<'a, K> {
ByteType::Unknown => return Ok(None),
}

// take the field names and the continuation (optional!)
let mut iter = EntryTagContinuousSubkeys::new(
self.id0,
self.netnode(),
ARRAY_SUP_TAG,
NSUP_TYPEINFO.into(),
)
.take(0x1000);
let Some(first_entry) = iter.next() else {
// Type information and field names are stored as interleaved continuation streams:
// NSUP_TYPEINFO + 2*n contains type bytes, while NSUP_TYPEINFO + 2*n + 1 contains field
// names. Either stream may end before the other one.
let type_info_start = u64::from(NSUP_TYPEINFO);
let mut chunks = Vec::new();
for entry in self.id0.sup_range(self.netnode(), ARRAY_SUP_TAG) {
let (index, value) = entry?;
let index = index.into_u64();
if index < type_info_start {
continue;
}
let offset = index - type_info_start;
if offset >= 0x1000 {
break;
}
chunks.push((offset, value));
}
let (til_raw, fields_raw) = collect_type_info_chunks(chunks);
if til_raw.is_empty() {
return Ok(None);
};
let mut til_raw: Vec<u8> = first_entry.value.to_vec();
}

// convert the value into fields
// usually this string ends with \x00, but maybe there is no garanty for that.
// TODO what if there is more fields that can fit a id0 entry
let field_names = if let Some(fields_entry) = iter.next() {
let value = parse_maybe_cstr(&fields_entry.value);
// Convert the reassembled field-name stream into individual fields. It usually ends with
// \x00, but there is no guarantee that the terminator is present.
let field_names = if !fields_raw.is_empty() {
let value = parse_maybe_cstr(&fields_raw);
crate::ida_reader::split_strings_from_array(value)
.ok_or_else(|| anyhow!("Invalid Fields for TIL Type"))?
} else {
// no fields
// TODO what if the type requires a continuation but it have no
// fields, does it just skip 0x3001? If so can't use
// EntryTagContinuousSubkeys above
vec![vec![]]
vec![]
};

// condensate the data continuation into a single buffer
til_raw.extend(iter.flat_map(|e| &e.value[..]));

// create the raw type
let til = Type::new_from_id0(info, &til_raw, field_names)?;
Ok(Some(til))
Expand All @@ -297,6 +297,66 @@ impl<'a, K: IDAKind> AddressInfo<'a, K> {
}
}

fn collect_type_info_chunks<'a>(
chunks: impl IntoIterator<Item = (u64, &'a [u8])>,
) -> (Vec<u8>, Vec<u8>) {
let mut result = [Vec::new(), Vec::new()];
let mut expected_offset = [0, 1];
let mut complete = [false, false];

for (offset, value) in chunks {
let stream = (offset & 1) as usize;
if complete[stream] {
continue;
}
if offset != expected_offset[stream] {
complete[stream] = true;
continue;
}
result[stream].extend_from_slice(value);
expected_offset[stream] += 2;
}

let [type_info, fields] = result;
(type_info, fields)
}

#[cfg(test)]
mod tests {
use super::collect_type_info_chunks;

#[test]
fn type_info_and_field_continuations_are_reassembled_independently() {
let chunks: [(u64, &[u8]); 5] = [
(0, b"type-0"),
(1, b"fields-0"),
(2, b"type-1"),
(3, b"fields-1"),
(4, b"type-2"),
];

let (type_info, fields) = collect_type_info_chunks(chunks);

assert_eq!(type_info, b"type-0type-1type-2");
assert_eq!(fields, b"fields-0fields-1");
}

#[test]
fn a_gap_ends_only_its_own_continuation_stream() {
let chunks: [(u64, &[u8]); 4] = [
(0, b"type-0"),
(1, b"fields-0"),
(3, b"fields-1"),
(4, b"not-type-1"),
];

let (type_info, fields) = collect_type_info_chunks(chunks);

assert_eq!(type_info, b"type-0");
assert_eq!(fields, b"fields-0fields-1");
}
}

pub fn all_address_info<'a, K: IDAKind>(
id0: &'a ID0Section<K>,
id1: &ID1Section<K>,
Expand Down
34 changes: 33 additions & 1 deletion src/id0/root_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use num_enum::{IntoPrimitive, TryFromPrimitive};
use num_traits::{WrappingAdd, WrappingSub};
use serde::Serialize;

use crate::ida_reader::IdbReadKind;
use crate::ida_reader::{IdbRead, IdbReadKind};
use crate::til::function::CCModel;
use crate::til::section::{
TILSectionExtendedSizeofInfo, TILSectionFlags, TILSectionHeader,
Expand Down Expand Up @@ -589,6 +589,8 @@ impl<K: IDAKind> RootInfo<K> {
let cc_size_l = input.read_u8()?;
let cc_size_ll = input.read_u8()?;
let cc_size_ldbl = input.read_u8()?;
let _extended_calling_convention =
read_extended_calling_convention(input, version)?;
let abibits = AbiOptions::new(input.unpack_dd()?)?;
let appcall_options = input.unpack_dd()?;

Expand Down Expand Up @@ -744,6 +746,36 @@ impl<K: IDAKind> RootInfo<K> {
}
}

fn read_extended_calling_convention(
input: &mut impl IdbRead,
version: u16,
) -> Result<Option<u32>> {
// IDA 9.2 added compiler_info_t::_new_callcnv after size_ldbl.
(version >= 920).then(|| input.unpack_dd()).transpose()
}

#[cfg(test)]
mod tests {
use super::read_extended_calling_convention;

#[test]
fn extended_calling_convention_was_added_in_ida_9_2() {
let mut old_input = &[0][..];
assert_eq!(
read_extended_calling_convention(&mut old_input, 919).unwrap(),
None
);
assert_eq!(old_input, &[0]);

let mut new_input = &[0][..];
assert_eq!(
read_extended_calling_convention(&mut new_input, 920).unwrap(),
Some(0)
);
assert!(new_input.is_empty());
}
}

/// General idainfo flags
#[derive(Debug, Clone, Copy, Serialize)]
pub struct Inffl(u8);
Expand Down
27 changes: 21 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,9 +182,25 @@ fn read_500_600_header<I: IdbRead>(
version: IDBSeparatedVersion,
input: &mut I,
) -> Result<IDBFormats> {
let id2_offset = input.read_u32()?;
ensure!(id2_offset == 0);
let checksums: [u32; 5] = bincode::deserialize_from(input)?;
// This field is not a section offset. IDA writes non-zero values here
// (for example, 2 in some version 3 databases), so keep it opaque until
// its meaning is known.
let _unknown = input.read_u32()?;
let checksums: [u32; 5] = bincode::deserialize_from(&mut *input)?;
let id2 = match version {
IDBSeparatedVersion::V1 => None,
IDBSeparatedVersion::V3 | IDBSeparatedVersion::V4 => {
let id2_offset = input.read_u32()?;
let id2_checksum = input.read_u32()?;
SeparatedSection::new_inner::<IDA32>(
id2_offset,
Some(id2_checksum),
)?
}
IDBSeparatedVersion::V5 | IDBSeparatedVersion::V6 => {
unreachable!("versions 5 and 6 use the extended header")
}
};

let id0 =
SeparatedSection::new::<IDA32>(&offsets[0..4], Some(checksums[0]))?;
Expand All @@ -196,7 +212,6 @@ fn read_500_600_header<I: IdbRead>(
SeparatedSection::new::<IDA32>(&offsets[12..16], Some(checksums[3]))?;
let til =
SeparatedSection::new::<IDA32>(&offsets[16..20], Some(checksums[4]))?;

#[cfg(feature = "restrictive")]
{
// TODO ensure the rest of the header is just zeros
Expand All @@ -218,7 +233,7 @@ fn read_500_600_header<I: IdbRead>(
nam,
seg,
til,
id2: None,
id2,
},
)))
} else {
Expand All @@ -232,7 +247,7 @@ fn read_500_600_header<I: IdbRead>(
nam,
seg,
til,
id2: None,
id2,
},
)))
}
Expand Down
52 changes: 52 additions & 0 deletions src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,58 @@ fn parse_idb_inner(file: PathBuf) {
}
}

#[test]
fn identify_v3_header_with_nonzero_unknown_field() {
let mut header = Vec::new();
header.extend_from_slice(b"IDA1");
header.extend_from_slice(&0u16.to_le_bytes());
for offset in [0x40u32, 0x80, 0xc0, 0, 0x100] {
header.extend_from_slice(&offset.to_le_bytes());
}
header.extend_from_slice(&0xAABB_CCDDu32.to_le_bytes());
header.extend_from_slice(&3u16.to_le_bytes());
header.extend_from_slice(&2u32.to_le_bytes());
for checksum in [1u32, 2, 3, 0, 4] {
header.extend_from_slice(&checksum.to_le_bytes());
}
header.extend_from_slice(&0x140u32.to_le_bytes());
header.extend_from_slice(&5u32.to_le_bytes());

let IDBFormats::Separated(IDAVariants::IDA32(sections)) =
identify_idb_file(&mut Cursor::new(header)).unwrap()
else {
panic!("expected a 32-bit separated IDB");
};

assert_eq!(sections.id0_location().unwrap().idb_offset(), 0x40);
assert_eq!(sections.id2_location().unwrap().idb_offset(), 0x140);
}

#[test]
fn identify_v1_header_without_id2_fields() {
let mut header = Vec::new();
header.extend_from_slice(b"IDA1");
header.extend_from_slice(&0u16.to_le_bytes());
for offset in [0x3eu32, 0x80, 0xc0, 0, 0x100] {
header.extend_from_slice(&offset.to_le_bytes());
}
header.extend_from_slice(&0xAABB_CCDDu32.to_le_bytes());
header.extend_from_slice(&1u16.to_le_bytes());
header.extend_from_slice(&0u32.to_le_bytes());
for checksum in [1u32, 2, 3, 0, 4] {
header.extend_from_slice(&checksum.to_le_bytes());
}

let IDBFormats::Separated(IDAVariants::IDA32(sections)) =
identify_idb_file(&mut Cursor::new(header)).unwrap()
else {
panic!("expected a 32-bit separated IDB");
};

assert_eq!(sections.id0_location().unwrap().idb_offset(), 0x3e);
assert!(sections.id2_location().is_none());
}

fn parse_idb_format<K: IDAKind, F: IDBFormat<K>, I: BufRead + Seek>(
filename: &str,
input: &mut I,
Expand Down
Loading