Skip to content

Commit 8e63876

Browse files
pl4ntyrussellbanks
authored andcommitted
Analyse Squirrel.Windows and Velopack
Signed-off-by: Tom Plant <tom.plant@devicie.com>
1 parent 28d1ca6 commit 8e63876

23 files changed

Lines changed: 1007 additions & 162 deletions

src/analysis/installers/burn/mod.rs

Lines changed: 26 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -71,39 +71,33 @@ impl Burn {
7171
let mut resource_directory = ResourceDirectory::new(section_reader)?;
7272

7373
let _rc_data = resource_directory
74-
.find_rc_data()
75-
.map_err(|_| BurnError::NotBurnFile);
76-
77-
let msi_entry = resource_directory
78-
.find_name_entry("MSI")
79-
.ok_or(BurnError::NotBurnFile)?;
80-
81-
let msi_directory_table = msi_entry
82-
.data(&mut resource_directory)?
83-
.table()
84-
.ok_or(BurnError::NotBurnFile)?;
85-
86-
return if let Some(msi_directory) = msi_directory_table.entries().next() {
87-
let msi_data_entry_offset = msi_directory.file_offset(resource_directory_offset);
88-
reader.seek(SeekFrom::Start(msi_data_entry_offset.into()))?;
89-
90-
let msi_data_entry = reader.read_t::<ImageResourceDataEntry>()?;
91-
let msi_offset = pe
92-
.section_table
93-
.to_file_offset(msi_data_entry.offset_to_data())?;
94-
95-
// Installers built with the Java Development Kit embed an MSI resource
96-
reader.seek(SeekFrom::Start(msi_offset.into()))?;
97-
let msi_reader = reader.take(msi_data_entry.size().into());
98-
let msi = Msi::new(msi_reader)?;
99-
Ok(Self {
100-
architecture: msi.architecture,
101-
manifest: None,
102-
msi: Some(msi),
103-
})
104-
} else {
105-
Err(BurnError::NotBurnFile)
74+
.navigate_to_rc_data()
75+
.map_err(|_| BurnError::NotBurnFile)?;
76+
77+
let msi_directory_table =
78+
resource_directory.navigate_to_directory_table_by_name("MSI")?;
79+
80+
let Some(msi_directory) = msi_directory_table.id_entries().next() else {
81+
return Err(BurnError::NotBurnFile);
10682
};
83+
84+
let msi_data_entry_offset = msi_directory.file_offset(resource_directory_offset);
85+
reader.seek(SeekFrom::Start(msi_data_entry_offset.into()))?;
86+
87+
let msi_data_entry = reader.read_t::<ImageResourceDataEntry>()?;
88+
let msi_offset = pe
89+
.section_table
90+
.to_file_offset(msi_data_entry.offset_to_data())?;
91+
92+
// Installers built with the Java Development Kit embed an MSI resource
93+
reader.seek(SeekFrom::Start(msi_offset.into()))?;
94+
let msi_reader = reader.take(msi_data_entry.size().into());
95+
let msi = Msi::new(msi_reader)?;
96+
return Ok(Self {
97+
architecture: msi.architecture,
98+
manifest: None,
99+
msi: Some(msi),
100+
});
107101
};
108102

109103
// Seek to and read wix burn stub

src/analysis/installers/exe.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ use color_eyre::Result;
44
use inno::{Inno, error::InnoError};
55
use winget_types::installer::{Installer, InstallerType};
66

7-
use super::{super::Installers, Burn, Nsis};
7+
use super::{super::Installers, Burn, Nsis, Squirrel};
88
use crate::{
99
analysis::installers::{
1010
burn::BurnError,
1111
nsis::NsisError,
1212
pe::{PE, VSVersionInfo},
13+
squirrel::SquirrelError,
1314
},
1415
traits::IntoWingetArchitecture,
1516
};
@@ -29,6 +30,7 @@ pub enum ExeType {
2930
Burn(Box<Burn>),
3031
Inno(Box<Inno>),
3132
Nsis(Nsis),
33+
Squirrel(Squirrel),
3234
Generic(Box<Installer>),
3335
}
3436

@@ -93,6 +95,19 @@ impl Exe {
9395
Err(error) => return Err(error.into()),
9496
}
9597

98+
match Squirrel::new(&mut reader, &pe) {
99+
Ok(squirrel) => {
100+
return Ok(Self {
101+
r#type: ExeType::Squirrel(squirrel),
102+
legal_copyright,
103+
product_name,
104+
company_name,
105+
});
106+
}
107+
Err(SquirrelError::NotSquirrelFile) => {}
108+
Err(error) => return Err(error.into()),
109+
}
110+
96111
Ok(Self {
97112
r#type: ExeType::Generic(Box::new(Installer {
98113
architecture: pe.winget_architecture(),
@@ -125,6 +140,7 @@ impl Installers for Exe {
125140
ExeType::Burn(burn) => burn.installers(),
126141
ExeType::Inno(inno) => inno.installers(),
127142
ExeType::Nsis(nsis) => nsis.installers(),
143+
ExeType::Squirrel(squirrel) => squirrel.installers(),
128144
ExeType::Generic(installer) => vec![*installer.clone()],
129145
}
130146
}

src/analysis/installers/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ mod msi;
55
pub mod msix_family;
66
pub mod nsis;
77
pub mod pe;
8+
pub mod squirrel;
89
pub mod utils;
910
mod zip;
1011

1112
pub use burn::Burn;
1213
pub use exe::Exe;
1314
pub use msi::Msi;
1415
pub use nsis::Nsis;
16+
pub use squirrel::Squirrel;
1517
pub use zip::Zip;

src/analysis/installers/pe/mod.rs

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ pub mod vs_version_info;
1010

1111
use std::{
1212
io,
13-
io::{Error, Read, Seek, SeekFrom},
13+
io::{Error, Read, Seek, SeekFrom, Take},
1414
};
1515

1616
pub use coff::CoffHeader;
@@ -23,7 +23,9 @@ pub use vs_version_info::VSVersionInfo;
2323
use crate::{
2424
analysis::installers::pe::{
2525
optional_header::DataDirectory,
26-
resource::{ImageResourceDataEntry, ResourceDirectory, SectionReader},
26+
resource::{
27+
IdOrName, ImageResourceDataEntry, ResourceDirectory, ResourceIter, SectionReader,
28+
},
2729
},
2830
read::ReadBytesExt,
2931
};
@@ -68,6 +70,17 @@ impl PE {
6870
})
6971
}
7072

73+
pub fn resources<R: Read + Seek>(&self, reader: R) -> io::Result<ResourceIter<R>> {
74+
let section_reader = self
75+
.resource_table()
76+
.ok_or_else(|| Error::other("No resource table"))?
77+
.section_reader(reader, &self.section_table)?;
78+
79+
let resource_directory = ResourceDirectory::new(section_reader)?;
80+
81+
Ok(ResourceIter::new(resource_directory))
82+
}
83+
7184
pub fn find_section(&self, name: [u8; 8]) -> Option<&SectionHeader> {
7285
self.section_table
7386
.sections()
@@ -104,6 +117,23 @@ impl PE {
104117
self.optional_header.data_directories.resource_table()
105118
}
106119

120+
pub fn find_resource_by_name<R>(&self, mut reader: R, name: &str) -> io::Result<Take<R>>
121+
where
122+
R: Read + Seek,
123+
{
124+
let resource = self
125+
.resources(&mut reader)?
126+
.find(|resource| resource.name() == Some(name))
127+
.ok_or_else(|| Error::other(format!("Resource not found: {name}")))?;
128+
129+
let resource_offset = self
130+
.section_table
131+
.to_file_offset(resource.offset_to_data())?;
132+
133+
reader.seek(SeekFrom::Start(resource_offset.into()))?;
134+
Ok(reader.take(resource.size().into()))
135+
}
136+
107137
pub fn vs_version_info<R>(&self, mut reader: R) -> io::Result<Vec<u8>>
108138
where
109139
R: Read + Seek,
@@ -123,11 +153,10 @@ impl PE {
123153

124154
let mut resource_directory = ResourceDirectory::new(section_reader)?;
125155

126-
let _version_info = resource_directory.find_version_info()?;
156+
let directory_table = resource_directory.navigate_to_version_info()?;
127157

128-
let version_entry = resource_directory
129-
.current_directory_table()
130-
.entries()
158+
let version_entry = directory_table
159+
.id_entries()
131160
.next()
132161
.ok_or_else(|| Error::other("No manifest entry found"))?;
133162

@@ -137,7 +166,7 @@ impl PE {
137166
.ok_or_else(|| Error::other("No manifest directory table found"))?;
138167

139168
let version_directory = version_directory_table
140-
.entries()
169+
.id_entries()
141170
.next()
142171
.ok_or_else(|| Error::other("No manifest directory found"))?;
143172

@@ -177,11 +206,10 @@ impl PE {
177206

178207
let mut resource_directory = ResourceDirectory::new(section_reader)?;
179208

180-
let _manifest = resource_directory.find_manifest()?;
209+
let manifest_directory_table = resource_directory.navigate_to_manifest()?;
181210

182-
let manifest_entry = resource_directory
183-
.current_directory_table()
184-
.entries()
211+
let manifest_entry = manifest_directory_table
212+
.id_entries()
185213
.next()
186214
.ok_or_else(|| Error::other("No manifest entry found"))?;
187215

@@ -191,7 +219,7 @@ impl PE {
191219
.ok_or_else(|| Error::other("No manifest directory table found"))?;
192220

193221
let manifest_directory = manifest_directory_table
194-
.entries()
222+
.id_entries()
195223
.next()
196224
.ok_or_else(|| Error::other("No manifest directory found"))?;
197225

src/analysis/installers/pe/optional_header/data_directory.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
use std::{fmt, io};
1+
use std::{
2+
fmt, io,
3+
io::{Read, Seek},
4+
};
25

36
use itertools::Itertools;
47
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, LittleEndian, U32};
58

6-
use super::super::SectionTable;
9+
use super::super::{SectionReader, SectionTable};
710

811
/// Represents a PE data directory.
912
///
@@ -47,6 +50,16 @@ impl DataDirectory {
4750
pub fn file_offset(self, section_table: &SectionTable) -> io::Result<u32> {
4851
section_table.to_file_offset(self.virtual_address())
4952
}
53+
54+
pub fn section_reader<R: Read + Seek>(
55+
self,
56+
mut reader: R,
57+
section_table: &SectionTable,
58+
) -> io::Result<SectionReader<R>> {
59+
let mut directory_offset = self.file_offset(section_table)?;
60+
61+
SectionReader::new(reader, directory_offset.into(), self.size().into())
62+
}
5063
}
5164

5265
impl fmt::Debug for DataDirectory {

src/analysis/installers/pe/resource/directory.rs

Lines changed: 43 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ use std::{
33
io::{Read, Seek},
44
};
55

6-
use super::{ImageResourceDirectoryEntry, ResourceDirectoryTable, ResourceType, SectionReader};
6+
use super::{
7+
ImageResourceDirectoryEntry, NamedImageResourceDirectoryEntry, ResourceDirectoryTable,
8+
ResourceType, SectionReader,
9+
};
710

811
pub struct ResourceDirectory<R: Read + Seek> {
912
reader: SectionReader<R>,
@@ -27,37 +30,58 @@ impl<R: Read + Seek> ResourceDirectory<R> {
2730
&self.current_directory_table
2831
}
2932

30-
pub fn find_rc_data(&mut self) -> io::Result<&ResourceDirectoryTable> {
31-
self.find_directory_table_by_id(ResourceType::RCData.id())
33+
pub fn navigate_to_rc_data(&mut self) -> io::Result<&ResourceDirectoryTable> {
34+
self.navigate_to_directory_table(ResourceType::RCData)
35+
}
36+
37+
pub fn navigate_to_manifest(&mut self) -> io::Result<&ResourceDirectoryTable> {
38+
self.navigate_to_directory_table(ResourceType::Manifest)
3239
}
3340

34-
pub fn find_manifest(&mut self) -> io::Result<&ResourceDirectoryTable> {
35-
self.find_directory_table_by_id(ResourceType::Manifest.id())
41+
pub fn navigate_to_version_info(&mut self) -> io::Result<&ResourceDirectoryTable> {
42+
self.navigate_to_directory_table(ResourceType::Version)
3643
}
3744

38-
pub fn find_version_info(&mut self) -> io::Result<&ResourceDirectoryTable> {
39-
self.find_directory_table_by_id(ResourceType::Version.id())
45+
pub fn navigate_to_directory_table(
46+
&mut self,
47+
resource_type: ResourceType,
48+
) -> io::Result<&ResourceDirectoryTable> {
49+
self.navigate_to_directory_table_by_id(resource_type.id())
4050
}
4151

42-
pub fn find_directory_table_by_id(&mut self, id: u32) -> io::Result<&ResourceDirectoryTable> {
43-
let directory_entry = *self
52+
pub fn navigate_to_directory_table_by_id(
53+
&mut self,
54+
id: u32,
55+
) -> io::Result<&ResourceDirectoryTable> {
56+
let mut directory_entry =
57+
self.current_directory_table
58+
.find_id_entry(id)
59+
.ok_or_else(|| {
60+
io::Error::new(
61+
io::ErrorKind::InvalidData,
62+
format!("{id} not found in current directory table"),
63+
)
64+
})?;
65+
66+
self.current_directory_table = directory_entry.data(self)?.table().unwrap();
67+
Ok(&self.current_directory_table)
68+
}
69+
70+
pub fn navigate_to_directory_table_by_name(
71+
&mut self,
72+
name: &str,
73+
) -> io::Result<&ResourceDirectoryTable> {
74+
let mut directory_entry = self
4475
.current_directory_table
45-
.find_id_entry(id)
76+
.find_name_entry(name)
4677
.ok_or_else(|| {
4778
io::Error::new(
4879
io::ErrorKind::InvalidData,
49-
format!("{id} not found in current directory table"),
80+
format!("{name} not found in current directory table"),
5081
)
5182
})?;
5283

53-
self.current_directory_table = directory_entry.data(self)?.table().unwrap();
84+
self.current_directory_table = directory_entry.entry().data(self)?.table().unwrap();
5485
Ok(&self.current_directory_table)
5586
}
56-
57-
pub fn find_name_entry(&mut self, name: &str) -> Option<ImageResourceDirectoryEntry> {
58-
self.current_directory_table
59-
.clone()
60-
.find_name_entry(self.reader_mut(), name)
61-
.copied()
62-
}
6387
}

0 commit comments

Comments
 (0)