Parsing package IDs

This commit is contained in:
Victor Lyuboslavsky
2024-09-06 09:49:27 -05:00
parent 031e5b9c71
commit d7f13295e9
9 changed files with 243 additions and 146 deletions
+7 -6
View File
@@ -33,9 +33,9 @@ func ExtractDebMetadata(r io.Reader) (*InstallerMetadata, error) {
return nil, fmt.Errorf("failed to advance to next file in archive: %w", err)
}
name := path.Clean(hdr.Name)
if strings.HasPrefix(name, "control.tar") {
ext := filepath.Ext(name)
filename := path.Clean(hdr.Name)
if strings.HasPrefix(filename, "control.tar") {
ext := filepath.Ext(filename)
if ext == ".tar" {
ext = ""
}
@@ -49,9 +49,10 @@ func ExtractDebMetadata(r io.Reader) (*InstallerMetadata, error) {
return nil, fmt.Errorf("failed to read all content: %w", err)
}
return &InstallerMetadata{
Name: name,
Version: version,
SHASum: h.Sum(nil),
Name: name,
Version: version,
PackageIDs: []string{name},
SHASum: h.Sum(nil),
}, nil
}
}
+1
View File
@@ -25,6 +25,7 @@ type InstallerMetadata struct {
BundleIdentifier string
SHASum []byte
Extension string
PackageIDs []string
}
// ExtractInstallerMetadata extracts the software name and version from the
+6 -4
View File
@@ -9,7 +9,7 @@ import (
"io"
"strings"
"github.com/sassoftware/relic/v7/lib/comdoc"
"github.com/sassoftware/relic/v8/lib/comdoc"
)
func ExtractMSIMetadata(r io.Reader) (*InstallerMetadata, error) {
@@ -77,10 +77,12 @@ func ExtractMSIMetadata(r io.Reader) (*InstallerMetadata, error) {
return nil, err
}
// MSI installer product information properties: https://learn.microsoft.com/en-us/windows/win32/msi/property-reference#product-information-properties
return &InstallerMetadata{
Name: strings.TrimSpace(props["ProductName"]),
Version: strings.TrimSpace(props["ProductVersion"]),
SHASum: h.Sum(nil),
Name: strings.TrimSpace(props["ProductName"]),
Version: strings.TrimSpace(props["ProductVersion"]),
PackageIDs: []string{strings.TrimSpace(props["ProductCode"])},
SHASum: h.Sum(nil),
}, nil
}
+5 -3
View File
@@ -50,10 +50,12 @@ func ExtractPEMetadata(r io.Reader) (*InstallerMetadata, error) {
if err != nil {
return nil, fmt.Errorf("error parsing PE version resources: %w", err)
}
name := strings.TrimSpace(v["ProductName"])
return applySpecialCases(&InstallerMetadata{
Name: strings.TrimSpace(v["ProductName"]),
Version: strings.TrimSpace(v["ProductVersion"]),
SHASum: h.Sum(nil),
Name: name,
Version: strings.TrimSpace(v["ProductVersion"]),
PackageIDs: []string{name},
SHASum: h.Sum(nil),
}, v), nil
}
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<installer-gui-script minSpecVersion="2">
<pkg-ref id="com.bozo.zeroinstallsize" installKBytes="0" packageIdentifier="com.bozo.zeroinstallsize.app">
<bundle-version>
<bundle CFBundleShortVersionString="1.2.3" CFBundleVersion="8.10.34.234040" id="com.bozo.zeroinstallsize" path="ZeroInstallSize.app"/>
</bundle-version>
</pkg-ref>
<product id="com.bozo.zeroinstallsize" version="1.2.3"/>
<title>ZeroInstallSize</title>
</installer-gui-script>
+45 -5
View File
@@ -123,6 +123,7 @@ type distributionPkgRef struct {
BundleVersions []distributionBundleVersion `xml:"bundle-version"`
MustClose distributionMustClose `xml:"must-close"`
PackageIdentifier string `xml:"packageIdentifier,attr"`
InstallKBytes string `xml:"installKBytes,attr"`
}
// distributionBundleVersion represents the bundle-version element
@@ -223,21 +224,55 @@ func parseDistributionFile(rawXML []byte) (*InstallerMetadata, error) {
return nil, fmt.Errorf("unmarshal Distribution XML: %w", err)
}
name, identifier, version := getDistributionInfo(&distXML)
name, identifier, version, packageIDs := getDistributionInfo(&distXML)
return &InstallerMetadata{
Name: name,
Version: version,
BundleIdentifier: identifier,
PackageIDs: packageIDs,
}, nil
}
// getDistributionInfo gets the name, bundle identifier and version of a PKG distribution file
func getDistributionInfo(d *distributionXML) (name string, identifier string, version string) {
func getDistributionInfo(d *distributionXML) (name string, identifier string, version string, packageIDs []string) {
var appVersion string
// find the package ids that have an installation size
var packageIDSet = make(map[string]struct{}, 1)
for _, pkg := range d.PkgRefs {
if pkg.InstallKBytes != "" && pkg.InstallKBytes != "0" {
var id string
if pkg.PackageIdentifier != "" {
id = pkg.PackageIdentifier
} else if pkg.ID != "" {
id = pkg.ID
}
if id != "" {
packageIDSet[id] = struct{}{}
}
}
}
if len(packageIDSet) == 0 {
// if we didn't find any package IDs with installation size, then grab all of them
for _, pkg := range d.PkgRefs {
var id string
if pkg.PackageIdentifier != "" {
id = pkg.PackageIdentifier
} else if pkg.ID != "" {
id = pkg.ID
}
if id != "" {
packageIDSet[id] = struct{}{}
}
}
}
for id := range packageIDSet {
packageIDs = append(packageIDs, id)
}
out:
// first, look in all the bundle versions for one that has a `path` attribute
// look in all the bundle versions for one that has a `path` attribute
// that is not nested, this is generally the case for packages that distribute
// `.app` files, which are ultimately picked up as an installed app by osquery
for _, pkg := range d.PkgRefs {
@@ -284,6 +319,11 @@ out:
identifier = d.Product.ID
}
// if package IDs are still empty, use the identifier as the package ID
if len(packageIDs) == 0 && identifier != "" {
packageIDs = append(packageIDs, identifier)
}
// for the name, try to use the title and fallback to the bundle
// identifier
if name == "" && d.Title != "" {
@@ -296,7 +336,7 @@ out:
// for the version, try to use the top-level product version, if not,
// fallback to any version definition alongside the name or the first
// version in a pkg-ref we find.
if version == "" && d.Product.Version != "" {
if d.Product.Version != "" {
version = d.Product.Version
}
if version == "" && appVersion != "" {
@@ -310,7 +350,7 @@ out:
}
}
return name, identifier, version
return name, identifier, version, packageIDs
}
// isValidAppFilePath checks if the given input is a file name ending with .app
+79 -44
View File
@@ -8,6 +8,7 @@ import (
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -53,112 +54,145 @@ func TestCheckPKGSignature(t *testing.T) {
func TestParseRealDistributionFiles(t *testing.T) {
tests := []struct {
file string
expectedName string
expectedVersion string
expectedBundleID string
file string
expectedName string
expectedVersion string
expectedBundleID string
expectedPackageIDs []string
}{
{
file: "distribution-1password.xml",
expectedName: "1Password.app",
expectedVersion: "8.10.34",
expectedBundleID: "com.1password.1password",
file: "distribution-1password.xml",
expectedName: "1Password.app",
expectedVersion: "8.10.34",
expectedBundleID: "com.1password.1password",
expectedPackageIDs: []string{"com.1password.1password"},
},
{
file: "distribution-chrome.xml",
expectedName: "Google Chrome.app",
expectedVersion: "126.0.6478.62",
expectedBundleID: "com.google.Chrome",
file: "distribution-chrome.xml",
expectedName: "Google Chrome.app",
expectedVersion: "126.0.6478.62",
expectedBundleID: "com.google.Chrome",
expectedPackageIDs: []string{"com.google.Chrome"},
},
{
file: "distribution-edge.xml",
expectedName: "Microsoft Edge.app",
expectedVersion: "126.0.2592.56",
expectedBundleID: "com.microsoft.edgemac",
file: "distribution-edge.xml",
expectedName: "Microsoft Edge.app",
expectedVersion: "126.0.2592.56",
expectedBundleID: "com.microsoft.edgemac",
expectedPackageIDs: []string{"com.microsoft.edgemac"},
},
{
file: "distribution-firefox.xml",
expectedName: "Firefox.app",
expectedVersion: "99.0",
expectedBundleID: "org.mozilla.firefox",
file: "distribution-firefox.xml",
expectedName: "Firefox.app",
expectedVersion: "99.0",
expectedBundleID: "org.mozilla.firefox",
expectedPackageIDs: []string{"org.mozilla.firefox"},
},
{
file: "distribution-fleet.xml",
expectedName: "Fleet osquery",
expectedVersion: "42.0.0",
expectedBundleID: "com.fleetdm.orbit",
file: "distribution-fleet.xml",
expectedName: "Fleet osquery",
expectedVersion: "42.0.0",
expectedBundleID: "com.fleetdm.orbit",
expectedPackageIDs: []string{"com.fleetdm.orbit.base.pkg"},
},
{
file: "distribution-go.xml",
expectedName: "Go",
expectedVersion: "go1.22.4",
expectedBundleID: "org.golang.go",
file: "distribution-go.xml",
expectedName: "Go",
expectedVersion: "go1.22.4",
expectedBundleID: "org.golang.go",
expectedPackageIDs: []string{"org.golang.go"},
},
{
file: "distribution-microsoft-teams.xml",
expectedName: "Microsoft Teams.app",
expectedVersion: "24124.1412.2911.3341",
expectedBundleID: "com.microsoft.teams2",
expectedPackageIDs: []string{"com.microsoft.teams2", "com.microsoft.package.Microsoft_AutoUpdate.app",
"com.microsoft.MSTeamsAudioDevice"},
},
{
file: "distribution-zoom.xml",
expectedName: "zoom.us.app",
expectedVersion: "6.0.11.35001",
expectedBundleID: "us.zoom.xos",
file: "distribution-zoom.xml",
expectedName: "zoom.us.app",
expectedVersion: "6.0.11.35001",
expectedBundleID: "us.zoom.xos",
expectedPackageIDs: []string{"us.zoom.pkg.videomeeting"},
},
{
file: "distribution-acrobatreader.xml",
expectedName: "Adobe Acrobat Reader.app",
expectedVersion: "24.002.20857",
expectedBundleID: "com.adobe.Reader",
expectedPackageIDs: []string{"com.adobe.acrobat.DC.reader.app.pkg.MUI", "com.adobe.acrobat.DC.reader.appsupport.pkg.MUI",
"com.adobe.acrobat.reader.DC.reader.app.pkg.MUI", "com.adobe.armdc.app.pkg"},
},
{
file: "distribution-airtame.xml",
expectedName: "Airtame.app",
expectedVersion: "4.10.1",
expectedBundleID: "com.airtame.airtame-application",
file: "distribution-airtame.xml",
expectedName: "Airtame.app",
expectedVersion: "4.10.1",
expectedBundleID: "com.airtame.airtame-application",
expectedPackageIDs: []string{"com.airtame.airtame-application"},
},
{
file: "distribution-boxdrive.xml",
expectedName: "Box.app",
expectedVersion: "2.38.173",
expectedBundleID: "com.box.desktop",
expectedPackageIDs: []string{"com.box.desktop.installer.desktop", "com.box.desktop.installer.local.appsupport",
"com.box.desktop.installer.autoupdater", "com.box.desktop.installer.osxfuse"},
},
{
file: "distribution-iriunwebcam.xml",
expectedName: "IriunWebcam.app",
expectedVersion: "2.8.8",
expectedBundleID: "com.iriun.macwebcam",
// Note: "com.iriun.pkg.multicam" is part of the installer package, but it is not actually installed by default.
// We can't reliably determine which packages are installed by the installer, so we just list all of them.
expectedPackageIDs: []string{"com.iriun.pkg.webcam.tmp", "com.iriun.pkg.multicam"},
},
{
file: "distribution-microsoftexcel.xml",
expectedName: "Microsoft Excel.app",
expectedVersion: "16.86",
expectedBundleID: "com.microsoft.Excel",
expectedPackageIDs: []string{"com.microsoft.package.Microsoft_Excel.app", "com.microsoft.package.Microsoft_AutoUpdate.app",
"com.microsoft.pkg.licensing"},
},
{
file: "distribution-microsoftword.xml",
expectedName: "Microsoft Word.app",
expectedVersion: "16.86",
expectedBundleID: "com.microsoft.Word",
expectedPackageIDs: []string{"com.microsoft.package.Microsoft_Word.app", "com.microsoft.package.Microsoft_AutoUpdate.app",
"com.microsoft.pkg.licensing"},
},
{
file: "distribution-miscrosoftpowerpoint.xml",
expectedName: "Microsoft PowerPoint.app",
expectedVersion: "16.86",
expectedBundleID: "com.microsoft.Powerpoint",
expectedPackageIDs: []string{"com.microsoft.package.Microsoft_PowerPoint.app", "com.microsoft.package.Microsoft_AutoUpdate.app",
"com.microsoft.pkg.licensing"},
},
{
file: "distribution-ringcentral.xml",
expectedName: "RingCentral.app",
expectedVersion: "24.1.32.9774",
expectedBundleID: "com.ringcentral.glip",
file: "distribution-ringcentral.xml",
expectedName: "RingCentral.app",
expectedVersion: "24.1.32.9774",
expectedBundleID: "com.ringcentral.glip",
expectedPackageIDs: []string{"com.ringcentral.glip"},
},
{
file: "distribution-zoom-full.xml",
expectedName: "Zoom Workplace",
expectedVersion: "6.1.1.36333",
expectedBundleID: "us.zoom.xos",
file: "distribution-zoom-full.xml",
expectedName: "Zoom Workplace",
expectedVersion: "6.1.1.36333",
expectedBundleID: "us.zoom.xos",
expectedPackageIDs: []string{"us.zoom.pkg.videomeeting"},
},
{
file: "test-zero-installkbytes.xml",
expectedName: "ZeroInstallSize.app",
expectedVersion: "1.2.3",
expectedBundleID: "com.bozo.zeroinstallsize",
expectedPackageIDs: []string{"com.bozo.zeroinstallsize.app"},
},
}
@@ -168,6 +202,7 @@ func TestParseRealDistributionFiles(t *testing.T) {
require.NoError(t, err)
metadata, err := parseDistributionFile(rawXML)
require.NoError(t, err)
assert.ElementsMatch(t, tt.expectedPackageIDs, metadata.PackageIDs)
require.Equal(t, tt.expectedName, metadata.Name)
require.Equal(t, tt.expectedVersion, metadata.Version)
require.Equal(t, tt.expectedBundleID, metadata.BundleIdentifier)