13.2 Inspecting .deb Files and Package State

Key Takeaways

  • dpkg -I file.deb prints a package's control information (dependencies, version, description) before you install it; dpkg -c file.deb lists the files it will place on disk
  • dpkg -L package lists files an installed package owns; dpkg -S /path/to/file answers the reverse question — which package owns that file
  • dpkg -s package shows the full status record for an installed package from /var/lib/dpkg/status, including Depends, Version, and Status
  • A .deb file is an ar archive containing three members: debian-binary, control.tar.*, and data.tar.*
  • dpkg --verify package compares installed files against recorded checksums, reporting altered files with rpm-style letter codes
Last updated: July 2026

Two Databases, Two Families of Commands

Every inspection question you can ask dpkg falls into one of two families, and the exam loves to test whether you know which family a flag belongs to:

  1. Questions about a .deb file sitting on disk — answered by handing dpkg a filename: dpkg -I (info, uppercase i) and dpkg -c (contents).
  2. Questions about packages already installed — answered by handing dpkg a package name or path: dpkg -L, dpkg -S, and dpkg -s.

Do not try to memorize these by letter case: -I is uppercase yet belongs to the file family, while -s is lowercase yet belongs to the installed family. The reliable test is what you hand the command — a path to a .deb, or the name of something already installed.

A reliable memory hook: with -I and -c you hand dpkg a file path; with -L, -S, and -s you hand it a package name or a path to resolve against the installed database in /var/lib/dpkg/.

Examining a .deb Before Installing It

Before trusting a downloaded package, look inside it. dpkg -I package.deb (--info) prints the control file — the package's metadata:

$ dpkg -I mypackage_1.2.3_amd64.deb
 new Debian package, version 2.0.
 size 154820 bytes: control archive=1412 bytes.
 Package: mypackage
 Version: 1.2.3
 Architecture: amd64
 Maintainer: Example Dev <dev@example.com>
 Depends: libfoo2 (>= 2.0), libc6 (>= 2.36)
 Section: utils
 Priority: optional
 Description: an example package

This is where you confirm the Depends line, the architecture, and the version before installing. The companion flag dpkg -c package.deb (--contents) lists every file the package will install, in tar-style long format:

$ dpkg -c mypackage_1.2.3_amd64.deb
drwxr-xr-x root/root         0 2025-11-02 10:14 ./
drwxr-xr-x root/root         0 2025-11-02 10:14 ./usr/bin/
-rwxr-xr-x root/root     98304 2025-11-02 10:14 ./usr/bin/mypackage
drwxr-xr-x root/root         0 2025-11-02 10:14 ./etc/
-rw-r--r-- root/root       412 2025-11-02 10:14 ./etc/mypackage.conf

The exam trap here is pairing: -I shows control information, -c shows file contents. Swapping them in a question stem is a classic distractor.

Querying Installed Packages

Once a package is installed, three queries cover most day-to-day needs:

  • dpkg -L package (--listfiles) lists every file the package installed, read from /var/lib/dpkg/info/package.list. Note that dpkg -L reflects what is recorded in the dpkg database, so it does not list extra files created by maintainer scripts, nor alternatives; dpkg -c by contrast shows exactly what is inside the .deb's data.tar.
  • dpkg -S /usr/bin/nmap (--search) answers the reverse question: which package owns this file? The answer comes back as nmap: /usr/bin/nmap. You can also pass a substring or glob (dpkg -S bin/ls), and dpkg prints every matching package:path pair. This is the tool you reach for when you find a mystery binary on a system during a forensic review.
  • dpkg -s package (--status) dumps the package's full record from /var/lib/dpkg/status: Status: install ok installed, Version, Depends, Conffiles with their md5sums, and the description. It is the installed-package equivalent of dpkg -I.

All of these are also available through dpkg-query (for example dpkg-query -L nmap), which is the scripted interface; the bare dpkg flags are shorthand for the same queries.

Verifying Installed Files: dpkg --verify

dpkg --verify package (also -V) compares the files on disk against the checksums dpkg recorded at install time and reports discrepancies using single-letter codes borrowed from rpm's verify format:

$ sudo dpkg --verify nmap
??5??????   /usr/bin/nmap

Each position in the code tests one attribute — the 5 position means the MD5 digest differs, i.e. the file's contents changed. dpkg implements only two of the nine positions — position 2 (file mode, shown as M) and position 3 (MD5 digest, shown as 5); every other position is always ?, because dpkg does not record size, mtime, or symlink targets. Missing files are reported as missing. One caveat matters for the exam: conffiles under /etc are expected to change, so a modified config file is not automatically suspicious. On a hardened Kali engagement machine, running dpkg --verify across key packages is a quick tamper check.

Anatomy of a .deb File

A .deb is not a tarball and not a zip — it is an ar archive, the same format used for static libraries. Inspect it directly:

$ ar t mypackage_1.2.3_amd64.deb
debian-binary
control.tar.zst
data.tar.zst

Three members, always:

MemberContents
debian-binaryA single line with the format version, 2.0
control.tar.*The control metadata file, md5sums, conffiles list, and maintainer scripts (preinst, postinst, prerm, postrm)
data.tar.*The actual payload — the files to be unpacked onto the filesystem

The compression on the tar members varies — gzip (.gz), xz (.xz), or zstd (.zst), with zstd now common in Kali — but the ar container is constant. You can extract members by hand with ar x package.deb control.tar.zst and then tar -tf control.tar.zst, which is exactly what dpkg-deb --info and dpkg-deb --contents do for you. The related tool dpkg-deb -b directory package.deb builds a .deb from a properly laid-out directory tree (a DEBIAN/control file plus the payload), and Kali's own packages are assembled this way by the packaging toolchain.

Knowing this anatomy explains the flag behavior: dpkg -I reads from control.tar, dpkg -c reads from data.tar, and installation is nothing more than unpacking data.tar onto / while running the scripts found in control.tar at the right moments.

The On-Disk Package Database

Everything dpkg knows lives under /var/lib/dpkg/, and knowing the layout pays off on the exam:

  • /var/lib/dpkg/status — the master database: one record per known package, exactly what dpkg -s prints.
  • /var/lib/dpkg/info/<package>.list — the file list behind dpkg -L.
  • /var/lib/dpkg/info/<package>.md5sums — the checksums behind dpkg --verify.
  • /var/lib/dpkg/info/<package>.{preinst,postinst,prerm,postrm} — the maintainer scripts from Section 13.3, kept after installation so dpkg can run the removal scripts even though the original .deb is long gone.

A practical workflow ties the query flags together: you spot /usr/bin/ncat on a system, run dpkg -S /usr/bin/ncat to learn it belongs to the ncat package, run dpkg -s ncat to see its version and dependencies, and dpkg -L ncat to enumerate everything else it installed. One more precision point: dpkg -S searches the database with substring or glob matching, so dpkg -S bin/ncat works even if you do not know the absolute path — but it can only find files that a package actually owns, so anything under /usr/local (the locally installed hierarchy) will correctly report no path found matching pattern.

Test Your Knowledge

During an incident review you find an unfamiliar executable at /usr/sbin/netmon. Which command tells you which installed package, if any, owns that file?

A
B
C
D
Test Your Knowledge

You extract a downloaded package with ar t tool_2.0_amd64.deb and see three members: debian-binary, control.tar.zst, and data.tar.zst. Where would you find the package's postinst script and Depends metadata?

A
B
C
D