I went nuts and decided to create a checksum verifier.
This is in three parts to let you customize it better. The first script just accumulates a table of known packages. The second script generates a list of packages (and the interesting files in them) based on the local files and local package repository. The output will most likely contain packages for many other architectures, so you should filter the output for proper package candidates. The third script is the workhorse: it eats lists of files and local or remote packages, and generates SHA1 checksums of the files inside those packages.
Here is the repository/file tree package list catalog script. This lets you grab the package list from a local tree (for example, from known good DVD/CD-ROM media), or from (the
ls-lR file in) a reliable repository. Save it as e.g.
package-list:
Code:
#!/bin/bash
IFS="
"
if [ $# -lt 1 ] || [ "$1" == "-h" ] || [ "$1" == "--help" ]; then
exec >&2
echo ""
echo "Usage: $0 [ -h | --help ]"
echo " $0 directories or repositories ..."
echo ""
echo "This script will scan the specified directories and"
echo "repositories, and list all packages in a concise from"
echo "to standard output."
echo ""
exit 1
fi
while [ $# -gt 0 ]; do
arg="$1"
shift 1
if [ -e "$arg" ]; then
find "$arg" -type f -name '*.deb' -printf '%f local %p\n' | sed -e 's|^\([^ _]*\)_[^ ]* |\1 |'
else
wget -O - "${arg%/}/ls-lR" | gawk -v "prefix=${arg%/}/" '
BEGIN {
RS="(\n\r?|\r\n?)[\t\n\v\f\r ]*"
FS="[\t\v\f ]+"
path = ""
}
/^\// {
pre = $0
gsub(/^\/+/, "", pre)
gsub(/\/*:*[\t ]*$/, "", pre)
path = prefix "/" pre "/"
gsub(/\/\/+/, "/", path)
gsub(/:\/+/, "://", path)
next
}
/^\.\// {
pre = $0
gsub(/^(\.\/)+/, "", pre)
gsub(/^\/+/, "", pre)
gsub(/\/*:*[\t ]*$/, "", pre)
path = prefix "/" pre "/"
gsub(/\/\/+/, "/", path)
gsub(/:\/+/, "://", path)
next
}
/\.deb$/ {
file = $(NF)
name = file
gsub(/_.*$/, "", name)
gsub(/^.*\//, "", name)
printf("%s remote %s%s\n", name, path, file)
}'
fi
done
exit 0
Then, the next script uses
dpkg-query to check which packages own the local files. It then looks at the package names in the table generated by the above script, and lists those that match, along with the name of the interesting file. Save this script as e.g.
locate-package:
Code:
#!/bin/bash
if [ "$1" == "-h" ] || [ "$1" == "--help" ] || [ $# -lt 2 ]; then
exec >&2
echo ""
echo "Usage: $0 [ -h | --help ]"
echo " $0 package-list file .."
echo ""
exit 1
fi
if [ -x "$1" ] || [ ! -r "$1" ] || [ ! -s "$1" ]; then
echo "$1: Cannot read package list file." >&2
exit 1
else
packages="$1"
shift 1
fi
errors=0
list=()
IFS="
"
OFS="
"
while [ $# -gt 0 ]; do
source="$1"
file="$1"
shift 1
if [ -z "$file" ]; then
continue
fi
while [ -h "$file" ]; do
old="$file"
file="`readlink "$file"`"
if [ "$old" == "$file" ] || [ -z "$file" ]; then
echo "$old: Broken symlink ($source)" >&2
errors=$[errors | 2]
continue 2
fi
done
if [ ! -f "$file" ]; then
echo "$file: No such file." >&2
errors=$[errors | 1]
continue
fi
matches=0
for spec in `dpkg-query -S "$file"` ; do
actual="${file#*:}"
package="${spec%%:*}"
package="${package//[\t ]/}"
echo "$package $file"
matches=$[matches + 1]
done
if [ $matches -lt 1 ]; then
echo "$file: No packages found!" >&2
errors=$[errors | 4]
fi
done | awk -v "catalog=$packages" '
BEGIN {
RS="[\t\v\f ]*(\r?\n|\n\r)[\t\n\v\f\r ]*"
FS="[\t\v\f ]+"
}
{
if (NF >= 2 && length($1) > 0 && length($2) > 0)
package[$2] = $1
}
END {
while ((getline < catalog) > 0)
for (file in package)
if ($1 == package[file])
print file, $2, $3
}
'
exit $errors
The final script is the one that takes the list of files and interesting local or remote packages, and generates the SHA1 checksums. Save this script as e.g.
package-checksums:
Code:
#!/bin/bash
if [ "$1" == "-h" ] || [ "$1" == "--help" ]; then
exec >&2
echo ""
echo "Usage: $0 [ -h | --help ]"
echo " $0 [ files .. ]"
echo ""
echo "This script reads in lines of form"
echo " /path/to/file local /path/to/package.deb"
echo " /path/to/file remote http://../package.deb"
echo "and compares and reports the SHA1 checksums."
echo ""
exit 0
fi
DIR="/tmp/checkpackage-`hostname -s`.$$"
trap "rm -rf '$DIR'" EXIT
if ! mkdir -m 0700 "$DIR" ; then
echo "$DIR: Cannot create temporary directory." >&2
exit 1
fi
errors=0
while read path type source ; do
package="$DIR/${source##*/}"
if [ ! -f "$package" ]; then
case "$type" in
local)
if ! cp -f "$source" "$package" >&2 ; then
rm -f "$package"
echo "$source: Cannot copy package." >&2
errors=$[errors | 1]
fi
;;
remote)
if ! wget -O "$package" "$source" >&2 ; then
rm -f "$package"
echo "$source: Cannot obtain package." >&2
errors=$[errors | 2]
fi
;;
*)
echo "$type: Unknown method to retrieve '$source'." >&2
errors=$[errors | 4]
;;
esac
fi
if [ -f "$package" ]; then
hash="`dpkg-deb --fsys-tarfile "$package" | tar -O -x ".$path" | sha256sum -b -`"
hash="${hash%% \*-}"
if [ -n "$hash" ]; then
echo "$hash *$path ($source)"
else
echo "$source: Package does not contain $path." >&2
errors=$[errors | 8]
fi
fi
done
exit $errors
It may be a bit cumbersome, but this way you can control each phase best. Note that the final script may transfer a lot of packages (although each package only once), so it's best to use trim at least the final list before running the third script.
If I wanted to check if my
/usr/bin/sudo and
/bin/su match the Debian upstream, I'd use the above scripts something like this (using Bash):
Code:
files="/bin/su /usr/bin/sudo"
# Save a list of known packages as debian.packages -- this repository is closest to me
./package-list http://ftp.funet.fi/pub/mirrors/ftp.debian.org/debian > debian.packages
# Generate a list of packages containing the interesting files
./locate-package debian.packages $files > check.list
# Get the checksums of matching i386 packages
grep -e i386 check.list | ./package-checksums > upstream.sha1
# Get the checksums of my local files
sha1sum -b $files > local.sha1
Then I'd compare
local.sha1 first to the logs, and then to the upstream.sha1 file.
There are certain rootkits that may have hooked sha1sum or the standard C libraries, in which case the
log and the output may not match. (I'm assuming RKHunter uses SHA1 checksums; I'm not sure, though.)
If you have a known good copy of all the packages in e.g. readonly media, you should be able to modify
the above (even using the third script only) to check the file hashes yourself.
Hope this helps,
Nominal Animal