use goblin::mach::{MachO, SingleArch};
use keyhog_core::{Chunk, ChunkMetadata};
pub(crate) fn resolve_section_name(resolved: Option<&str>, sh_name: usize) -> &str {
match resolved {
Some(n) => n,
None => {
if sh_name != 0 {
let _event =
crate::record_skip_event(crate::SourceSkipEvent::BinarySectionNameUnresolved);
}
""
}
}
}
pub(crate) fn extract_sections(bytes: &[u8], path: &str) -> Option<Vec<Chunk>> {
use goblin::Object;
let obj = match Object::parse(bytes) {
Ok(o) => o,
Err(_) => return None, };
let mut chunks = Vec::new();
let target_sections = &[
".rodata",
".rdata",
".data",
".const",
".cstring",
"__cstring",
"__const",
"__data",
];
match obj {
Object::Elf(elf) => {
for sh in &elf.section_headers {
let name = resolve_section_name(elf.shdr_strtab.get_at(sh.sh_name), sh.sh_name);
if target_sections.contains(&name) {
let start = sh.sh_offset as usize;
let end = start.saturating_add(sh.sh_size as usize).min(bytes.len());
if start < end {
let section_bytes = &bytes[start..end];
let strings = crate::binary::extract_printable_strings(
section_bytes,
crate::strings::MIN_PRINTABLE_STRING_LEN,
);
if !strings.is_empty() {
chunks.push(Chunk {
data: crate::strings::join_sensitive_strings(&strings, "\n"),
metadata: ChunkMetadata {
base_offset: 0,
base_line: 0,
source_type: format!("binary:elf:{name}").into(),
path: Some(path.into()),
commit: None,
author: None,
date: None,
mtime_ns: None,
size_bytes: None,
decoded_span: None,
},
});
}
}
}
}
}
Object::PE(pe) => {
for section in &pe.sections {
let name = match std::str::from_utf8(§ion.name) {
Ok(n) => n.trim_end_matches('\0'),
Err(_error) => {
let _event = crate::record_skip_event(
crate::SourceSkipEvent::BinarySectionNameUnresolved,
);
""
}
};
if target_sections.contains(&name) {
let start = section.pointer_to_raw_data as usize;
let end = start
.saturating_add(section.size_of_raw_data as usize)
.min(bytes.len());
if start < end {
let section_bytes = &bytes[start..end];
let strings = crate::binary::extract_printable_strings(
section_bytes,
crate::strings::MIN_PRINTABLE_STRING_LEN,
);
if !strings.is_empty() {
chunks.push(Chunk {
data: crate::strings::join_sensitive_strings(&strings, "\n"),
metadata: ChunkMetadata {
base_offset: 0,
base_line: 0,
source_type: format!("binary:pe:{name}").into(),
path: Some(path.into()),
commit: None,
author: None,
date: None,
mtime_ns: None,
size_bytes: None,
decoded_span: None,
},
});
}
}
}
}
}
Object::Mach(goblin::mach::Mach::Binary(macho)) => {
append_macho_sections(&mut chunks, &macho, bytes, path, target_sections);
}
Object::Mach(goblin::mach::Mach::Fat(fat)) => {
let arches = match fat.arches() {
Ok(arches) => arches,
Err(_error) => {
let _event = crate::record_skip_event(
crate::SourceSkipEvent::BinarySectionNameUnresolved,
);
Vec::new()
}
};
for (index, arch) in arches.iter().enumerate() {
let Some(arch_bytes) = checked_fat_arch_slice(bytes, arch.offset, arch.size) else {
let _event = crate::record_skip_event(
crate::SourceSkipEvent::BinarySectionNameUnresolved,
);
continue;
};
match fat.get(index) {
Ok(SingleArch::MachO(macho)) => append_macho_sections(
&mut chunks,
&macho,
arch_bytes,
path,
target_sections,
),
Ok(SingleArch::Archive(_)) => {}
Err(_error) => {
let _event = crate::record_skip_event(
crate::SourceSkipEvent::BinarySectionNameUnresolved,
);
}
}
}
}
_ => {}
}
if chunks.is_empty() {
None
} else {
Some(chunks)
}
}
fn append_macho_sections(
chunks: &mut Vec<Chunk>,
macho: &MachO<'_>,
bytes: &[u8],
path: &str,
target_sections: &[&str],
) {
for seg in &macho.segments {
let sections = match seg.sections() {
Ok(s) => s,
Err(_error) => {
let _event =
crate::record_skip_event(crate::SourceSkipEvent::BinarySectionNameUnresolved);
Vec::new()
}
};
for (section, _) in sections {
let name = match section.name() {
Ok(n) => n,
Err(_error) => {
let _event = crate::record_skip_event(
crate::SourceSkipEvent::BinarySectionNameUnresolved,
);
""
}
};
if target_sections.contains(&name) {
let start = section.offset as usize;
let end = start.saturating_add(section.size as usize).min(bytes.len());
if start < end {
let section_bytes = &bytes[start..end];
let strings = crate::binary::extract_printable_strings(
section_bytes,
crate::strings::MIN_PRINTABLE_STRING_LEN,
);
if !strings.is_empty() {
chunks.push(Chunk {
data: crate::strings::join_sensitive_strings(&strings, "\n"),
metadata: ChunkMetadata {
base_offset: 0,
base_line: 0,
source_type: format!("binary:macho:{name}").into(),
path: Some(path.into()),
commit: None,
author: None,
date: None,
mtime_ns: None,
size_bytes: None,
decoded_span: None,
},
});
}
}
}
}
}
}
fn checked_fat_arch_slice(bytes: &[u8], offset: u32, size: u32) -> Option<&[u8]> {
let start = offset as usize;
start
.checked_add(size as usize)
.and_then(|end| bytes.get(start..end))
}