Skip to content

Package Manager Forensics on Linux: dpkg, APT, RPM and DNF

Use dpkg, APT, RPM and DNF logs and databases to date installs, verify file integrity and find binaries no package owns on a compromised Linux host.

Published on 6 min read

Package managers keep a detailed, timestamped record of what changed on a system and a hash inventory of every file they installed. For an investigator that is two things at once: a change log that can date the arrival of tooling such as nmap, socat or a compiler, and an integrity baseline that can reveal a trojanised sshd or ps. This guide covers the Debian family (dpkg and APT) and the RHEL family (RPM, YUM and DNF), with notes on snap, Flatpak and pip.

Work on a mounted read-only copy of the evidence where possible (see acquiring Linux evidence). The paths below are shown under /mnt/evidence.

Debian and Ubuntu: dpkg and APT

/var/log/dpkg.log

dpkg.log is written by dpkg itself, so it records every package operation regardless of which frontend triggered it: apt, apt-get, unattended-upgrades, a GUI or a manual dpkg -i. Each line starts with a local-time timestamp without a time zone.

2026-09-14 10:22:31 startup archives unpack
2026-09-14 10:22:31 install socat:amd64 <none> 1.7.4.4-2
2026-09-14 10:22:31 status half-installed socat:amd64 1.7.4.4-2
2026-09-14 10:22:32 status unpacked socat:amd64 1.7.4.4-2
2026-09-14 10:22:32 configure socat:amd64 1.7.4.4-2 <none>
2026-09-14 10:22:32 status installed socat:amd64 1.7.4.4-2

The actions you care about are install, upgrade, remove and purge. The file is rotated by logrotate (typically monthly, keeping about a year), so collect dpkg.log.1 and the compressed dpkg.log.*.gz copies too.

zcat -f /mnt/evidence/var/log/dpkg.log* | grep -E ' (install|upgrade|remove|purge) ' | sort

/var/log/apt/history.log and term.log

APT adds context that dpkg lacks: the full command line and, when the command ran through sudo, the requesting user.

Start-Date: 2026-09-14  10:22:29
Commandline: apt-get install -y socat
Requested-By: deploy (1001)
Install: socat:amd64 (1.7.4.4-2)
End-Date: 2026-09-14  10:22:32

term.log in the same directory holds the terminal output of each transaction, including maintainer script messages and errors. Correlate Requested-By with sudo entries in auth.log or the journal, as described in Linux log forensics. A package that appears in dpkg.log but in no APT transaction was most likely installed with dpkg -i from a local .deb, which is worth asking about: where did that file come from?

/var/lib/dpkg: the package database

PathWhat it holdsForensic use
/var/lib/dpkg/statusEvery known package, its state, version and conffile hashesCurrent inventory, packages left in deinstall or half-configured states
/var/lib/dpkg/info/<pkg>.listFiles installed by the packageFile ownership without running dpkg
/var/lib/dpkg/info/<pkg>.md5sumsMD5 of each non-conffileIntegrity baseline
/var/lib/dpkg/info/<pkg>.postinst and friendsMaintainer scriptsScripts run as root at install time; malicious .deb files abuse them
/var/backups/dpkg.status.*Daily backups of the status fileEarlier inventory states to diff

The inode timestamps of .list files are a useful fallback when logs have been rotated away or deleted: a .list file is created when the package is unpacked. Multi-arch packages use names such as libc6:amd64.list.

Verifying files with dpkg --verify and debsums

dpkg --verify (short form -V) checks installed files against the stored MD5 sums and prints only failures. Its output imitates the RPM format, but only the digest test is implemented, so other positions show ?.

dpkg --root=/mnt/evidence --verify 2>/dev/null
# ??5??????   /usr/sbin/sshd
# ??5?????? c /etc/ssh/sshd_config

A c marks a conffile, which administrators are expected to edit. A 5 on a binary in /usr/sbin is a serious lead. debsums (a separate package) does the same job with friendlier output: debsums -c lists changed files and -a includes conffiles, and it also accepts --root for offline use.

Know the blind spots. Packages without an .md5sums file are not checked, files dpkg never installed are ignored entirely, and on merged-/usr systems dpkg -S /bin/ls may fail to match because the package records /usr/bin/ls or vice versa.

RHEL, Rocky, Alma and Fedora: RPM and DNF

The RPM database

RPM stores its database under /var/lib/rpm. RHEL 8 uses Berkeley DB files; RHEL 9 and current Fedora use SQLite (rpmdb.sqlite). Recent Fedora releases moved the database to /usr/lib/sysimage/rpm and leave /var/lib/rpm as a symlink, so check that the link resolves inside your mount rather than on the analysis host. Query an offline image with --root, ideally from an analysis system whose rpm version supports the same database backend.

rpm --root=/mnt/evidence -qa --last | head -30
rpm --root=/mnt/evidence -qi openssh-server | grep -E 'Install Date|Signature'

--last sorts by install time, which quickly surfaces packages added around the incident window.

rpm -Va output

rpm -Va verifies every installed file against size, digest, permissions, ownership and more. Each character position is one test:

FlagTestTypical meaning when set
SFile size differsContent replaced
MMode (permissions or file type) differsAdded SUID bit, changed permissions
5Digest differsContent changed
DDevice major/minor mismatchRare; device nodes
LSymlink target differsRedirected link
UOwner differschown
GGroup differschgrp
TModification time differsEdited or timestomped file
PCapabilities differAdded file capabilities

A . means the test passed and ? means it could not be run (often a permissions issue). An attribute letter before the path (c config, d doc, g ghost, l license, r readme) gives context, and missing flags a deleted file.

S.5....T.    /usr/bin/ps
.M.......    /usr/bin/find
S.5....T.  c /etc/ssh/sshd_config

S.5....T. on /usr/bin/ps is exactly what a replaced binary looks like; an M on find warrants a check for a newly set SUID bit, a classic privilege escalation backdoor covered in hunting Linux persistence. Filter out expected config noise with grep -v ' c /', but review it separately afterwards.

DNF and YUM logs and history

SourceDistrosNotes
/var/log/yum.logRHEL/CentOS 7Lines like Sep 14 10:22:31 Installed: ... carry no year
/var/log/dnf.log, dnf.rpm.logRHEL 8/9, Fedora with DNF 4dnf.rpm.log lists each package action
/var/lib/dnf/history.sqliteDNF 4Transaction history database
/var/log/dnf5.logFedora with DNF 5Newer log location and format

On a live DNF 4 system, dnf history list and dnf history info <id> show each transaction with the user and command line. Offline, query the SQLite history directly:

sqlite3 -readonly /mnt/evidence/var/lib/dnf/history.sqlite \
  "SELECT id, datetime(dt_begin,'unixepoch'), user_id, cmdline FROM trans ORDER BY id;"

Timestamps here are epoch seconds and therefore UTC, while dnf.log and yum.log use the host's local time. Normalise before you merge them into a super timeline.

Finding files no package owns

Attacker tools dropped into system paths rarely come with a package. Query ownership for each executable:

# Debian family
find /mnt/evidence/usr/bin /mnt/evidence/usr/sbin -type f | while read -r f; do
  p="${f#/mnt/evidence}"
  dpkg --root=/mnt/evidence -S "$p" >/dev/null 2>&1 || echo "UNOWNED $p"
done

# RHEL family
rpm --root=/mnt/evidence -qf /usr/sbin/sshd-helper
# file /usr/sbin/sshd-helper is not owned by any package

Expect legitimate hits: /usr/local, vendor agents, alternatives symlinks and files generated at install time. Extend the sweep to /usr/lib, /lib/modules, /etc/cron.*, /etc/systemd/system and library directories, where unowned shared objects may indicate an ld.so.preload implant.

Other package sources

  • snap: packages live as squashfs images in /var/lib/snapd/snaps/; snap changes on a live host and the snapd journal show install history.
  • Flatpak: system installs under /var/lib/flatpak, per-user under ~/.local/share/flatpak.
  • pip: each package has a *.dist-info directory with a RECORD file listing SHA-256 hashes of installed files and an INSTALLER file. Packages under /usr/local/lib/python3*/ on Debian are not dpkg-managed and will always show as unowned.

Limitations

Package databases are evidence, not ground truth. An attacker with root can rewrite .md5sums files, alter the RPM database, or install a trojanised package through the normal package manager so it verifies cleanly. Logs can be edited or truncated. For critical binaries, download the exact package version from a trusted mirror on a clean system, extract it (dpkg-deb -x or rpm2cpio | cpio -idm) and compare hashes directly. Also remember that verification only covers the file on disk: a binary that was swapped, executed and restored leaves no trace here, which is why memory and process evidence still matter.

Key takeaways

  • dpkg.log records every dpkg operation; apt/history.log adds the command line and sudo user. A gap between them points to a manual dpkg -i.
  • dpkg --verify, debsums -c and rpm -Va quickly surface modified binaries; learn the S M 5 D L U G T P flags.
  • Unowned files in system paths (dpkg -S, rpm -qf) are strong leads for dropped tooling.
  • Mind time zones: dpkg, dnf and yum logs are local time; DNF history timestamps are epoch UTC; yum.log has no year.
  • A clean verification is weak evidence, because root can rewrite the database. Compare against trusted repository packages for anything critical.