Skip to content

Add support for analysing Setup Factory installers#26

Draft
Tom Plant (pl4nty) wants to merge 4 commits into
mainfrom
claude/setup-factory-analysis-2yb6iz
Draft

Add support for analysing Setup Factory installers#26
Tom Plant (pl4nty) wants to merge 4 commits into
mainfrom
claude/setup-factory-analysis-2yb6iz

Conversation

@pl4nty

@pl4nty Tom Plant (pl4nty) commented Jul 7, 2026

Copy link
Copy Markdown
Member

Summary

Adds detection and analysis of Indigo Rose Setup Factory installers, such as DIALux evo.

Setup Factory setup executables are a self-extracting stub wrapping the irsetup.exe run-time, with a compressed archive in the PE overlay. This detector:

  1. Detects the installer from the 16-byte Setup Factory 8/9 overlay signature (with the run-time assembly manifest Setup Factory Run-time and the Created with Setup Factory version-info comment kept as fallbacks for older/repacked stubs).
  2. Parses the archive in the overlay to recover the compiled setup script (irsetup.dat) and reads its session variable table — the real values the author entered in the designer (%ProductName%, %CompanyName%, %ProductVer%).
  3. Emits an exe installer with the documented /S silent switch and an AppsAndFeaturesEntries built from the recovered metadata.

Why parse the binary instead of the PE version info

The setup stub's PE version info describes the run-time, not the packaged app — and authors frequently leave it at the Setup Factory defaults (gloCOM/Communicator would otherwise report DisplayName: Setup Factory Runtime, publisher Indigo Rose Corporation). The real product name/publisher/version live in the compiled script inside the overlay archive, so that's what we read. The archive format, XOR obfuscation and compression handling follow CybercentreCanada/sfextract.

Notes:

  • The script is LZMA- or LZMA2-compressed, decoded via the already-vendored liblzma. Large embedded files are seeked past so only the small script (well under 1 MB) is buffered — no full-payload extraction.
  • %ProductVer% is author-defined and is sometimes a bare major number (gloCOM ships "4"; the real version lives in a custom variable), so it's only used when it looks like a real dotted version.

Changes

  • New src/analysis/installers/setup_factory/ module:
    • mod.rs — detection, best-effort metadata extraction, installer emission.
    • archive.rs — walks the overlay archive and decompresses irsetup.dat.
    • script.rs — parses the session variable table (with unit tests).
  • Registered the module in src/analysis/installers/mod.rs and wired SetupFactory into the Exe detection chain in src/analysis/installers/exe.rs.

Verification

Tested end-to-end with komac analyze against real Setup Factory installers — see the results comment below. cargo build, cargo clippy (-D warnings), and cargo test (session-variable parser) all pass.

🤖 Generated with Claude Code

https://claude.ai/code/session_012BX4kXe76SvBKY3BySbzRN

Detect Indigo Rose Setup Factory installers (e.g. DIALux evo). The setup
stub embeds the `irsetup.exe` run-time, whose assembly manifest carries
`<description>Setup Factory Run-time</description>`, and the builder stamps
`Created with Setup Factory <version>` into the `Comments` version-info
field. Either signal identifies the installer.

Emits an `exe` installer with the `/S` silent switch and populates the
ARP entry from the version info (product name, publisher, version).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012BX4kXe76SvBKY3BySbzRN
The setup stub's version info describes the Setup Factory run-time (and is
often left at the builder's defaults, e.g. ProductName "Setup Factory
Runtime" published by Indigo Rose Corporation), not the packaged
application. The real Apps & Features / uninstall entry (DisplayName,
DisplayVersion, ProductCode, ...) is written by the compiled setup script
inside the payload, which isn't recoverable from the PE.

Emitting DisplayName/Publisher/DisplayVersion from the version info
therefore produced ARP entries that don't match the registry - the real
winget manifests for these packages (e.g. DIAL.DIALux-evo) carry no such
entries. Drop the extraction and emit only the reliably-known fields: the
exe installer type and the /S silent switch. Version info is still read
solely to detect the "Created with Setup Factory" Comments marker.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012BX4kXe76SvBKY3BySbzRN

Copy link
Copy Markdown
Member Author

Test results

Ran komac analyze against a range of real Setup Factory installers. All accessible ones are correctly detected as InstallerType: exe with Silent: /S / SilentWithProgress: /S.

Installer Link Detected Arch
DIALux evo https://cdn.dialux.com/evo/DIALux_evo_14_0.exe ✅ exe · /S x86¹
LDraw All-In-One-Installer² https://www.ldraw.org/files/aioi/LDraw_All-In-One-Installer_2023-02_setup_64Bit_v1.exe ✅ exe · /S x64
gloCOM 7.5.0 https://downloads.bicomsystems.com/desktop/glocom/public/7.5.0/glocom/gloCOM-7.5.0.exe ✅ exe · /S x64
Communicator 7.5.0 https://downloads.bicomsystems.com/desktop/glocom/public/7.5.0/communicator/Communicator-7.5.0.exe ✅ exe · /S x64
Safeguard PDF Writer v4 https://downloads.locklizard.com/SafeguardPDFWriter_v4.exe ✅ exe · /S x86
Safeguard PDF Viewer v3 https://downloads.locklizard.com/SafeguardPDFViewer_v3.exe ✅ exe · /S x86
Safeguard PDF Writer Enterprise v5 https://downloads.locklizard.com/SafeguardPDFWriter_Enterprise_v5.exe ✅ exe · /S x86
OutCALL 2.0 https://github.com/bicomsystems/outcall2/releases/download/v2.0/OutCALL-2.0.exe ⚠️ not tested³

Example output (DIALux evo), which matches the shape of the published DIAL.DIALux-evo manifest (exe + Silent: /S):

InstallerType: exe
InstallModes:
- interactive
- silent
- silentWithProgress
InstallerSwitches:
  Silent: /S
  SilentWithProgress: /S

Notes

¹ Architecture is read from the setup stub's PE machine type. DIALux ships a 32-bit stub that installs a 64-bit app, so it reports x86 where the published manifest says x64. This is a pre-existing limitation of exe architecture detection (the payload's target isn't recoverable from the stub) and isn't specific to this change.

² The exact 2023-02 build has been removed from ldraw.org (404). I verified against the current 2026-01 AIOI, which is the same Setup Factory installer family.

³ The OutCALL asset is a GitHub release download. This session's egress policy scopes GitHub to devicie/*, so github.com downloads for other owners are intercepted, and add_repo rejects cross-owner adds. I didn't route around the policy. It's a standard Setup Factory installer and will be handled identically to the others.

On AppsAndFeaturesEntries

An earlier revision of this PR populated DisplayName / Publisher / DisplayVersion from the stub's PE version info. Testing surfaced that this is wrong: gloCOM/Communicator/LDraw leave the stub's version info at the Setup Factory defaults, so they'd have reported DisplayName: Setup Factory Runtime published by Indigo Rose Corporation. Even where authors customise it (DIALux, Locklizard), the version-info values don't equal the registry uninstall keys that AppsAndFeaturesEntries are meant to mirror — the published DIAL.DIALux-evo manifest carries no such entries at all. The detector now emits no ARP data rather than guessing.


Generated by Claude Code

Rather than reading the setup stub's PE version info - which describes the
Setup Factory run-time and is frequently left at the builder's defaults
(e.g. gloCOM/Communicator reported "Setup Factory Runtime" published by
"Indigo Rose Corporation") - walk the Setup Factory 8/9 archive in the PE
overlay to recover the compiled setup script (irsetup.dat) and its session
variable table. That table holds the values the author entered in the
designer: %ProductName%, %CompanyName% and %ProductVer%.

The archive layout and XOR/compression handling follow the format
documented by CybercentreCanada/sfextract. The script is LZMA- or
LZMA2-compressed (decoded via the already-vendored liblzma); large embedded
files are seeked past so only the small script is buffered. Detection now
keys off the 16-byte overlay signature, with the run-time manifest and
version-info comments kept as fallbacks.

The recovered product name and publisher populate AppsAndFeaturesEntries.
The version variable is author-defined and sometimes a bare major number,
so it is only used when it looks like a real dotted version.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012BX4kXe76SvBKY3BySbzRN
@pl4nty Tom Plant (pl4nty) force-pushed the claude/setup-factory-analysis-2yb6iz branch from 884e97d to aca026b Compare July 7, 2026 05:13

Copy link
Copy Markdown
Member Author

Updated results — metadata now parsed from the overlay archive

Following the feedback to parse the binary rather than the stub's PE version info, the detector now walks the Setup Factory archive, decompresses irsetup.dat, and reads the session-variable table. komac analyze output against the real installers:

Installer Link DisplayName Publisher DisplayVersion
DIALux evo https://cdn.dialux.com/evo/DIALux_evo_14_0.exe DIALux DIAL GmbH 5.14.0.4
LDraw AIOI¹ https://www.ldraw.org/files/aioi/LDraw_All-In-One-Installer_2023-02_setup_64Bit_v1.exe LDraw (64 Bit) LDraw.org
gloCOM 7.5.0 https://downloads.bicomsystems.com/desktop/glocom/public/7.5.0/glocom/gloCOM-7.5.0.exe gloCOM Bicom Systems
Communicator 7.5.0 https://downloads.bicomsystems.com/desktop/glocom/public/7.5.0/communicator/Communicator-7.5.0.exe Communicator
Safeguard PDF Writer v4 https://downloads.locklizard.com/SafeguardPDFWriter_v4.exe Locklizard Safeguard - PDF Writer Locklizard Ltd. 4.0.24
Safeguard PDF Viewer v3 https://downloads.locklizard.com/SafeguardPDFViewer_v3.exe Locklizard Safeguard - PDF Viewer Locklizard Ltd. 3.0.2.231
Safeguard PDF Writer Enterprise v5 https://downloads.locklizard.com/SafeguardPDFWriter_Enterprise_v5.exe Locklizard Safeguard - PDF Writer Enterprise Locklizard Ltd. 5.0.46
OutCALL 2.0 https://github.com/bicomsystems/outcall2/releases/download/v2.0/OutCALL-2.0.exe not tested²

All emit InstallerType: exe with Silent: /S. This is a clear improvement over the earlier PE-version-info approach — gloCOM/Communicator previously reported Setup Factory Runtime published by Indigo Rose Corporation; they now show their real names, and LDraw (which had no usable version info) now yields LDraw (64 Bit) / LDraw.org.

Notes

¹ The exact 2023-02 build is gone from ldraw.org (404); verified against the current 2026-01 AIOI, same Setup Factory family. Its %ProductVer% isn't a dotted version, so no DisplayVersion is emitted.

² OutCALL is a GitHub release asset; this session's egress policy scopes GitHub to devicie/* and add_repo rejects cross-owner adds, so it couldn't be downloaded. Not routed around.

On the empty/omitted fields

  • Communicator has no publisher and gloCOM/Communicator no version because those values are genuinely absent or non-versioned in the project (%CompanyName% is empty; %ProductVer% is a bare "4" while the real version sits in a custom variable). %ProductVer% is only used when it looks like a real dotted version, so the bogus "4" is dropped rather than emitted.

Known limitation

Architecture still comes from the setup stub's PE machine type, so DIALux reports x86 (32-bit stub) though the app is 64-bit. Recovering the true target would require decompressing and inspecting a payload binary; left out of scope here.


Generated by Claude Code

}
}

fn has_archive_signature<R: Read + Seek>(reader: &mut R, overlay_start: u64) -> bool {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if this is only called once, we should inline it

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 484018c — inlined into new(). (The manifest/comments fallback that also lived in this file is gone too, so detection is now just the inlined signature check.)


Generated by Claude Code

Comment on lines +59 to +66
// The overlay signature is the definitive marker, but fall back to the run-time manifest and
// version-info comments so older (Setup Factory 7) or repacked stubs are still recognised.
if !has_archive
&& !is_runtime_manifest(pe, &mut reader)
&& !has_setup_factory_comments(pe, &mut reader)
{
return Err(SetupFactoryError::NotSetupFactoryFile);
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we remove this? which of your tests actually needs it? if we're going to keep it, we should document the tests + Factory versions that trigger it

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed in 484018c. No test needed it — all seven test installers (DIALux evo, LDraw AIOI, gloCOM, Communicator, and the three Locklizard Safeguard installers) carry the 16-byte overlay signature, so detection now keys off that alone. The fallback was speculative (Setup Factory 7 / repacked stubs) with no coverage.


Generated by Claude Code

//! ..\x0d%ProductName%\x06gloCOM\x01\x00\x00\x00..\x0d%CompanyName%\x0dBicom Systems..
//! ```
//!
//! Entries are separated by a short, version-dependent run of bytes, so rather than decode that

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there any documentation or code that could decode these bytes? I wonder if they have useful data

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dug into these. Between entries the bytes are 01 00 00 00 <XX XX 01> 00 00 00: the leading 01 is a constant type/flag, and the middle three bytes are constant within a given installer but differ between installers (6e 81 01 for Locklizard PDF Writer, b4 83 01 for gloCOM) — i.e. a per-build marker/checksum, not per-variable data. No product metadata is hidden there; the useful content is the name/value pairs we already extract. Neither sfextract nor SFUnpacker decode this table (they only handle the CSetupFileData file table), so there's no reference decoder to borrow from.


Generated by Claude Code


/// A file count above this is treated as an embedded `lua5.1.dll` special file rather than a real
/// count, matching `sfextract`'s heuristic.
const MAX_PLAUSIBLE_FILE_COUNT: u32 = 1000;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

which test cases have this? is the real issue that we're not parsing a specific Factory version properly?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All seven test installers take this path — it's the normal Setup Factory 9 layout, not a version mis-parse. After irsetup.exe, SF9 embeds a second special file (lua5.1.dll, ~350 KB) before the file table. The u32 read here to disambiguate is the low half of that DLL's 64-bit size (~350000), hence >1000; a genuine file count is small. Expanded the doc comment in 484018c to spell this out.


Generated by Claude Code


/// Upper bound on the decompressed script size we are willing to buffer (scripts are well under a
/// megabyte; this guards against a malformed size field).
const MAX_SCRIPT_SIZE: i64 = 64 << 20;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is necessary

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed — removed in 484018c. The script is now read with a bounded take(size).read_to_end(), which grows with the data instead of pre-allocating from the size field, so the cap isn't needed.


Generated by Claude Code

- Detect solely from the 16-byte overlay signature and inline the check;
  drop the run-time manifest / version-info comment fallback, which no test
  exercised (all samples carry the signature).
- Read the script with a bounded take().read_to_end() instead of
  pre-allocating from the size field, removing the size-cap guard.
- Document that the embedded lua5.1.dll branch is taken by every tested
  installer (it is the normal Setup Factory 9 layout, not a version
  mis-parse): the leading u32 is the low half of the DLL's 64-bit size.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012BX4kXe76SvBKY3BySbzRN
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants