Signature-du-Terroir

Signature-du-Terroir

Construct a signature of the installed software state or check the integrity of the installation using a previously made signature.

Usage: signduterre.py [options] FILE1 FILE2 ...

Options:

  -h, --help            show this help message and exit
  -s HEX, --salt=HEX    Enter salt in cleartext. If not given, a hexadecimal
                        salt will be suggested. The SUGGESTED[=N] keyword will
                        cause the selection of the suggested string. N is the
                        number of salts generated (default N=1). If N>1, all
                        will be printed and a random one will be used to
                        generate the signature (selection printed to STDERR).
  -a, --all-salts-pattern
                        Use all salts in sequence, randomly replace salts with
                        incorrect ones in the output to create a pattern of
                        failing hashes indicated by a corresponding integer
                        number. Depends on '--salt SUGGESTED=N'. Implies
                        --total-only.
  -p TEXT, --passphrase=TEXT
                        Enter passphrase in cleartext, the keyword
                        SUGGESTED[=N] will cause the suggested passphrase to
                        be used. If N>1, N passphrases will be printed to
                        STDERR and a random one will be used (selection
                        printed to STDERR). Entering the name of an existing
                        file (or '-' for STDIN) will cause it to be read and a
                        random passphrase found in the file will be used
                        (creating a signature), or they will all be used in
                        sequence (--check-file).
  -c FILE, --check-file=FILE
                        Check contents with the output of a previous run from
                        file or STDIN ('-'). Except when the --quiet option is
                        given, the previous output will contain all
                        information needed for the program, but not the
                        passphrase and the --execute option.
  -i FILE, --input-file=FILE
                        Use names from FILE or STDIN ('-'), use one filename
                        per line.
  -o FILE, --output-file=FILE
                        Print to FILE instead of STDOUT.
  --file-source=PATH    Read all files from PATH. The PATH-string is prepended
                        to every plain file-path that is read for a signature.
                        Remote files can be checked with
                        'ssh://<user>@<host>[/path]'. A shell command that
                        prints out the file can be entered as '$(<cmd>)'. The
                        filepath will be substituted for any '{}' string in
                        the command, or appended tot the command (without
                        white-space). The option overrules any File source
                        specification in the --check-file.
  -P FILE, --Private-file=FILE
                        Print private information (passwords etc.) to FILE
                        instead of STDERR.
  -u USER, --user=USER  Execute $(cmd) as USER, default 'nobody' (root/sudo
                        only)
  -S, --Status          For each file, add a line with unvarying file status
                        information: st_mode, st_ino, st_dev, st_uid, st_gid,
                        and st_size (like the '?' prefix, default False)
  --Status-values=MODE  Status values to print for --Status, default MODE is
                        'fmidugs' (file, mode, inode, device, uid, gid, size).
                        Also available (n)l(inks) a(time), (m)t(ime), and
                        c(time).
  -t, --total-only      Only print the total hash, unsets --detailed-view
                        (default True)
  -d, --detailed-view   Print hashes of individual files, is unset by --total-
                        only (default False)
  -e, --execute         Interpret $(cmd) (default False)
  --execute-args=ARGS   Arguments for the $(cmd) commands ($1 ....)
  -n, --no-execute      Explicitely do NOT Interpret $(cmd)
  --import=FILE         Import python modules (comma separated list without extension)
  --print-textdump      Print printable character+hexadecimal dump of input
                        bytes to STDERR for debugging purposes
  --message=TEXT        Add a comment message about the test
  -m, --manual          Print a short version of the manual and exit
  --manual-long         Print the long version of the manual and exit
  --manual-html         Print the manual in HTML format and exit
  --manual-make         Print the examples in the manual as a makefile and
                        exit
  -r, --release-notes   Print the release notes and exit
  -l, --license         Print license text and exit
  -v, --verbose         Print more information on output
  -q, --quiet           Print minimal information (hide filenames). If the
                        output is used with --check-file, the command line
                        options and arguments must be repeated.

FILE1 FILE2 ... Names and paths of one or more files to be checked. All file arguments in SdT accept '-' as the STDIN file (ie, piped data). Can use ssh://<user>@<host>/path pseudo-URLs for checking files at remote sites. Arguments of any type can take an appended range parameter '[<start>:<end>:<offset>]' or '[<start>:+<length>:<offset>]'. <offset>+<start> bytes are skipped and only <length>=<end>-<start> bytes are written. Leaving out the second argument, ie, '[<start>:]', means all bytes after <start> to the end of the file or stream are used. The ':<offset>' argument is optional. All <start>, <end>, <length>, and <offset> arguments are eval(uat)ed by the Python interpreter as expressions. This means ranges can be can be entered in decimal (default), hexadecimal (0x0000..), octal (0o0000..), and binary (0B0000..) representations.

Single expression byte rangess can each be enclosed in ()-brackets, and this is obligatory if a ':' character is used in an expression. It is allowed to use balanced []-brackets in expressions inside the byte range slices. Python evaluation takes place INSIDE the current Python environment. This means there is access to all imported modules including those imported with the --import option.
THIS COULD BE A SECURITY CONCERN AND THE '--execute' OPTION IS REQUIRED TO CALL ANY FUNCTION IN BYTE RANGE SLICE EXPRESSIONS

Any name starting with a '$', eg, $PATH, will be interpreted as an environmental variable or a command according to the bash conventions: '$ENV' and '${ENV}' as variables, '$(cmd;cmd...)' as system commands (bash --restricted -c 'cmd;cmd...' PID). Where PID the current Process ID is (available as positional parameter $0). Other parameters can be entered with the --execute-args option ($1 etc). Do not forget to enclose the arguments in single ''-quotes! The commands are scanned for unwanted characters and these are removed (eg, ' and \, however, escaping $ is allowed, eg, '\$1'). The use of '$(cmd;cmd...)' requires explicit use of the -e or --execute option.

Note that byte range slices '$(cmd)[<start>:<end>]' do work, but only after the command has completed. So, the file version, '/dev/kmem[0xc04838a0:+88]', will simply use 88 bytes as in '$(dd if=/dev/kmem bs=1 skip=3225958560 count=88)'. However, '$(dd if=/dev/kmem bs=1)[0xc04838a0:+88]' will first read all of /dev/kmem, and only then extract the 88 bytes. In general, this is not the desired procedure (/dev/kmem contains all of the physical RAM). Note that the remote '--file-soource=ssh://...' option preserves the file slice behavior as the file reads are changed into the equivalent 'dd skip=<start> count=<length>' commands.

Any string '@(python code)' will be evaluated as python 3 code. The '--execute' option is obligatory. Note that the outer ()-brackets are removed. You can extend the program by importing modules with the '--import <module>,<module>,....' option. The python code will be interpreted as a function body, complete with obligatory return statement(s), and wrapped in a function definition. This function will be executed in a separate namespace and the 'return'ed value will be exported and hashed. The current PID is available as 'argv[0]' and the --execute-args argument values are available as list elements 'argv[1]', 'argv[2]', etc. @() statements are executed inside the running signduterre program and cannot be used to querry a remote system with ssh:// pseudo-URL constructs.

If executed as root or sudo, $(cmd;cmd...) will be executed as 'sudo -H -u <user>' which defaults to --user nobody ('--user root' is at your own risk). This will obviously not work when invoked as non-root/sudo. --user root is necessary when you need to check privileged information, eg, you want to check the MBR with '$(dd if=/dev/hda bs=512 count=1 | od -X)' However, as you might use --check-file with files you did not create yourself, it is important to be warned if commands are to be executed.

Interpretation of $() ONLY works if the -e or --execute options are entered. signduterre.py can easily be adapted to automatically use the setting in the check-file. However, this is deemed insecure and commented out in the distribution version.

The -n or --no-execute option explicitely supress the interpretation of $(cmd) arguments.

Meta information from stat() on files is signed when the filename is preceded by a '?'. '?./signduterre.py' will extract (st_mode, st_ino, st_dev, st_nlinks, st_uid, st_gid, st_size) and hash a line of these data (visible with --verbose). The --Status option will automatically add such a line in front of every file. Note that '?' is implied for directories. Both '/' and '?/' produce a hash of, eg,:

stat(/) = [st_mode=041775, st_ino=2, st_dev=234881026, st_uid=0, st_gid=80, st_size=1360]

The --Status-values=<mode> option selects which status values will be used: f(ile), m(ode), i(node), d(evice), u(id), g(id), s(ize), (n)l(inks), a(time), (m)t(ime), and c(time). Default is --Status-values='fmidugs'. Note that nlinks of a directory include every file in the directory, so this option can check whether files have been added to a directory.

Arguments enclosed in []-brackets will be hidden in the output. That is, '[/proc/self/exe]' will show up as '[1]' in the output (or '[n]' with n the number of the hidden argument), equivalent to the use of the --quiet option. This means the hidden arguments must be entered again when using the --check-file (-c) option.

Signature-du-Terroir

A very simple tool to generate a signature that can be used to test the integrity of files and "states" in a running installation. signduterre.py constructs a signature of the current system state and checks installation state with a previously made signature. The files are hashed with a passphrase to allow detection of compromised systems while running on the same system. The signature checking can be subverted, but the flexibillity of signduterre.py and the fact that the output of any command can be tested should hamper automated root-kit attacks.

signduterre.py writes a total SHA-256 hash to STDOUT of all the files and commands entered as arguments. It can also write a hash for each individual file (insecure). The output of a signature can be send to a file and later used to check with --check-file. Hashes are calculated with a hashed salt + passphrase sequence pre-pended to create unpredictable hashes. This procedure ensures that an attacker does not know whether or not the correct passphrase has been entered. An attacker can only know when to supply the requested hash values if she knows the passphrase or has copies available of all the tested files and output of commands to calculate the hashes on the fly.

The Problem

The problem SdT tries to solve is how to test whether your system has been compromised when you can only use the potentially compromised system? The solution is to store a password encrypted signature (or fingerprint) of your system when you are sure it is in a good state. Then you check whether the system can still distinguish between correct and incorrect passwords when it regenerates the signature. The trick is to use the right data (ie, questions) to generate the signature.

The underlying idea is that some bits have to be changed to compromise a system. That is, program files have been altered, settings and accounts changed, new processes are running or existing processes altered. The most common situation is that some system programs have been changed to hide the traces of the attack. For instance, the ls, find, and stat commands are altered to hide the existence of new files and programs, and the netstat and ps commands or the /proc pseudo file system are changed to hide the malicious processes that are running. Such wholescale adaptations of running systems can be executed using standard, off-the-shelf application suits, so called rootkits. There are applications that can detect common (known) rootkits and other malicious programs, eg, chkrootkit (www.chkrootkit.org) and rootkit hunter (www.rootkit.nl). However, these rootkit detectors also use existing commands on the potentially compromised system, so a rootkit can hide from them too.

There are two obvious directions to guard against rootkits. One is to continuously run a process that looks for attempts to install a rootkit and other malicious activities. The other is to take a snapshot of the system in a known good state, and then flag changes in relevant areas, eg, like Tripwire (http://sourceforge.net/projects/tripwire/) and Radmind (http://rsug.itd.umich.edu/software/radmind/). Signature-du-Terroir takes the second route, it creates a signature of a set of relevant files and command output, and checks later whether these have not been changed. However, when running such a test on a compromised system, the attacker can theoretically "fool" any (automated) test. In practise, time and other precious resources will limit what an attacker can accomplish. The idea is to raise the bar for rootkits high enough to make them not worthwhile. SdT tries to make using signatures easy (cheap) and subverting it difficult (expensive).

As an illustration of the problem SdT treis to solve, take the sha256sum command which generates file hashes (signatures) using the SHA256 algorithm. Hashes can be generated and checked with this command:

# Use of sha256sum to check integrity of ps and ls commands
$ sha256sum /bin/ps /bin/ls > ps-ls.sh256
$ sha256sum -c ps-ls.sh256

A compromised file will show up as FAILED. This is ok for unintentional changes to the files. However, a malicious attacker could easily replace /usr/bin/sha256sum with a program that would replace the hash of malicious replacements of these files with the hash sums of the original files. There are three easy ways of doing that. Either simply say 'ok' when checking the file, print out the stored old hash value whenever an altered file is requested by name, or look for the hash of the new, malicious replacement and print out the old hash sum instead. The former two are easy to circumvent, the last one is somewhat less easy.

The first solution to these avoidance strategies is to generate the signatures with a passphrase and random string (salt). As long as the attacker does not know the passphrase, the only way to subvert SdT is to store the original bits in the files and calculate the signature the moment SdT is called. As the attacker does not know when the correct password or salt is entered, it is not possible to simply answer OK or repeat the stored earlier results instead of calculating them de-novo.

To be able to serve up the original bits, instead of the bits used on the compromised system, when asked for the hashes, the attacker must divert attempts to read the files by SdT, but not at other moments. There are many ways to do this, eg, running python in a chroot-jail, changing python itself, changing other programs. To accommodate these diversion strategies, SdT allows to read data from each and every command that can supply it. So, a binary file can be entered by name, with eg, cat, dd, perl, python, ruby, or read from the /proc system (if it is a running process), or from STDIN or shell subprocesses. For instance, to protect against running in a chroot-jail, the inode number and device of the root directory can be read from /proc/self/root, or /proc/<PID>/root, or simply from /.

Signature creation: Passphrases, salts, and hashes

Good passphrases are difficult to remember, so their plaintext form should be protected. To protect the passphrase against rainbow and brute force attacks, the passphrase is concatenated to a salt phrase and hashed before use (SHA-256).

The salt phrase is requested when constructing a signature. In interactive use, an 8 byte hexadecimal (= 16 character) salt from /dev/urandom is suggested. If '--salt SUGGESTED' is entered on the command line as the salt, the suggested value will be used. The salt is printed in plaintext to the output. The salt will make it more difficult to determine whether the same passphrase has been used to create different signatures.

At the bottom, a 'TOTAL HASH' line will be printed that hashes all the lines printed for the files. This includes the file names as printed on the hash lines. It is not inconceivable that existing signature files could have been compromised in ways that might be missed when checking the signature. The total hash will point out such changes.

SECURITY

When run on a compromised system, signduterre.py can be subverted if the attacker keeps a copy of all the files and command outputs, and reroutes the open() and stat() functions, or simply delegating signduterre.py to a chroot jail with the original system. In principle, signduterre.py only checks whether the computer responds identically to when the signature file was made. There is no theoretic barrier against a compromised computer perfectly simulating the original system when tested, but behaving adversely at other times. Except for running from clean boot media (USB?), I know of no theoretical sound solution to this problem.

However, this scenario assumes the use of unlimited resources and time. Inside a limited, real computer system, the attacker must make compromises on what can and what cannot be simulated with the available time and hardware. The idea behind signduterre.py is to "ask difficult questions" that increase the cost of simulating the original system high enough to make detection of successful attacks likely.signduterre.py simply intends to raise the bar high enoug. One point is to store the times needed to create the original hashes. This timing can later be used to see whether the new timings are reasonable. If the same hardware takes considerably longer to perform the same calculations, or needs a much longer delay before it starts, the tester might want to see where this time is spent.

Signature-du-Terroir works on the assumption that any attacker in control of a compromised system cannot predict whether the passphrase entered is correct or not. An attacker can always intercept the in- and output of signduterre. When running with --check-file, this means the program can be made to print out OK irrespective of the tests. A safe use of signduterre.py is to start with a random number of incorrect passphrases and see whether they fail. Alternatively, and easier, is to add a number of unused salts to the check-file and let the attacker guess which one is correct.

THE CORRECT USE OF signduterre.py IS TO ENTER A RANDOM NUMBER OF INCORRECT PASSPHRASES OR SALTS FOR EACH TEST AND SEE WHETHER IT FAILS AT THE CORRECT INSTANCES!

On a compromised system, signduterre.py's detailed file testing (--detailed-view) is easily subverted. With a matched file hash, the attacker will know that the correct passphrase has been entered and can print out the stored hashes or 'ok's for the rest of the checks. So if the attacker keeps any entry in the signature file uncompromised, she can intercept the output, test the password on the unchanged entry and substitute the requested hashes for the output if the hash of that entry matches.

When checking for root-kits and other malware, it is safest to compare the signature files from a different, clean, system. But then you would not need signduterre.py anyway. If you have to work on the system itself, only use the -t or --total-only options to create signatures with a total hash and without individual file hashes. Such a signature can be used to check whether the system is unchanged. Another signature file WITH A DIFFERENT PASSPHRASE can then be used to identify the individual files that have changed. If a detailed signature file has the same passphrase, an attacker could use that other file to read the individual file hashes to check whether the correct passphrase was entered.

Using the --check-file option in itself is UNsafe. An attacker simply has to print out 'OK' to defeat the check. This attack can be foiled by making it unpredictable when signduterre.py should return 'OK'. This can be done by using a list of salts or passphrases where only one of them (or none!) is correct. Any attacker will have to guess when to return 'OK'.

As generating and entering wrong passphrases and salts is tedious, users have to be supported in correct use of SdT. To assist users, the '--salt SUGGESTED=<N>' option will generate a number N of salts. When checking, each of these salts is tried in turn. An attacker that is unable to simulate the uncompromised system will have to guess which one of the salts is the correct one, and whether or not the passphrase is correct. This increases the chances of detecting compromised systems. If this is not enough guess work, the '-a', '--all-salts-pattern' option will use all salts in sequence to generate total hashes, but random salts will be changed in the output. This generates a pattern of failed salt tests. This pattern is translated into a bit pattern and printed as an integer ([Fail, Fail, OK, Fail, OK, OK, Fail, OK] = 00101101 (least significant first) = 10110100 (unsigned bin) = 180). On creation of a signature, this number is printed to STDERR, on checking (--check-file) it is printed to STDOUT (note that the number will never become 0 or all Fail). So for '--salt SUGGESTED=<N> --all-salts-pattern' the probability of guessing the correct output goes from 1/N to 1/(2^N - 1). Note that '--all-salts-pattern' will work, but is pointless, without '--salt SUGGESTED=<N>' with N>1.

The '--passphrase SUGGESTED=N' option will generate and print N passphrases. One of these is chosen at random for the signature. The number of the chosen passphrase is printed on STDERR with the passwords. When checking a file, the stored passphrases can be read in again, either by entering the passphrase file after the --passphrase option ('--passphrase <passphrase file>'), or directly from the --check-file. signduterre.py will print out the result for each of the passphrases.

Note, that storing passphrases in a file and feeding it to signduterre.py is MUCH less secure than just typing them in. Moreover, it might completely defeat the purpose of signduterre.py. If future experiences cast any more doubt on the security of this option, it will be removed.

For those who want to know more about what an "ideal attacker" can do, see:
Ken Thompson "Reflections on Trusting Trust"
http://cm.bell-labs.com/who/ken/trust.html
http://www.ece.cmu.edu/~ganger/712.fall02/papers/p761-thompson.pdf

David A Wheeler "Countering Trusting Trust through Diverse Double-Compiling"
http://www.acsa-admin.org/2005/abstracts/47.html

and the discussion of these at Bruce Schneier's 'Countering "Trusting Trust"'
http://www.schneier.com/blog/archives/2006/01/countering_trus.html

Manual

The intent of signduterre.py is to ensure that the signature cannot be subverted even if the system has been compromised by an attacker that has obtained root control over the computer and any existing signature files.

signduterre.py asks for a passphrase which is PRE-pended to every file before the hash is constructed (unless the passphrase is entered with an option). As long as the passphrase is not compromised, the hashes cannot be reconstructed. A randomly generated, unpadded base-64 encoded 16 Byte password (ie, ~22 characters) is suggested in interactive use. If '--passphrase SUGGESTED' is entered on the command line or no passphrase is entered when asked, the suggested value will be used. This value is printed to STDERR (the screen or 2) for safe keeping. Please, make sure you store the printed passphrase. For instance:

# make: example1
# Simple system sanity test using the 'which' command to establish the paths
$ python3 signduterre.py --passphrase SUGGESTED --salt SUGGESTED --detailed-view \
`which python3 bash ps ls find stat` 2> test-20090630_11-14-03.pwd > test-20090630_11-14-03.sdt
$ python3 signduterre.py --passphrase test-20090630_11-14-03.pwd --check-file test-20090630_11-14-03.sdt

The first command will store the passphrase (and all error messages) in a file 'Signature_20090630_11-14-03.pwd' and the check-file in 'Signature_20090630_11-14-03.sdt'. The second line will test the signature. The signature will be made of the files used for the commands python3, bash, ps, ls, find, and stat. These files are found using the 'which' command.

Working with remote systems

It is not secure to store files with the passphrase on the system you want to check. However, you could pipe STDERR to some safe site.

# Send passphrase over ssh tunnel to safe site
$ python3 signduterre.py --passphrase SUGGESTED --salt SUGGESTED `which bash python3` \
-o test-safe-store.sdt 2>&1 | ssh user@safe.host.site 'dd of=/home/user/safe/test-safe-store.pwd'

As the security of the passphrases is important and off-site storrage of files is often prudent or convenient, this tunneling construct has been automated in all in- and output as a pseudo-URL: 'ssh://<user>@<host></path>', eg, 'ssh://user@safe.host.site/home/user/safe/test-safe-store.pwd'. It is not possible to enter a password in such a pseudo-URL, so the automatical login into the host system must be configured in SSH.
Note: There are severe security risks involved when using SSH to login into another system if the originating system is compromised.

The pseudo-url can be used with the --output-file, --Private-file, --input-file, --check-file, --passphrase options as well as for the actual file, ${ENV}, and $(cmd) arguments used to determine the signatures. The latter allows to check files on remote systems, or to repeat a check from a remote system using the --file-source option (only works with plain files, ${ENV}, and $(cmd), not for @(python code), directories, or --Status arguments). For instance:

# Use ssh:// pseudo-url to send passphrase to safe.host.site
$ python3 signduterre.py --passphrase SUGGESTED --salt SUGGESTED `which bash python3` \
-o ssh://user@safe.host.site/home/user/safe/test-safe-store.sdt \
-P ssh://user@safe.host.site/home/user/safe/test-safe-store.pwd
# Check files on remote compromised.host.site while running test program on safe.host.site
$ python3 signduterre.py --passphrase test-safe-store.pwd --check-file test-safe-store.sdt \
--file-source ssh://user@compromised.host.site

To execute a remote $(cmd) argument, write $(ssh://<user>@<host>/cmd). Be aware that nested "-quotes might cause problems. ${ENV} can be written as ${ssh://<user>@<host>/ENV}. When using a --file-source argument that starts with 'ssh://', the $(cmd) and ${ENV} commands are internally rewritten into the above form. In both forms, as well as the arguments entered with --execute-args, any '$' and '"' symbols are protected by '\$' and '"' to be evaluated at the host system, as they would be evaluated locally by the ssh command line. This might not always work out as planned, so take care when using these pseudo-URLS. Note that no <path> argument will be used.

The next example uses the ssh:// pseudo-URL to read the data in an alternative way on localhost. Obviously, storing the plain text passphrase on the same system makes it a rather pointless excersize. The example only works if your have (open)SSH server and clients installed and appended the '~/.ssh/id_dsa.pub' or '~/.ssh/id_rsa.pub' file to '~/.ssh/authorized_keys', and you used ssh-add or another application to open the key.

# make: ssh1
# Use ssh:// pseudo-url to read data in an alternative way
$ python3 signduterre.py --passphrase SUGGESTED --salt SUGGESTED -v -d -e `which dd` '$(cat `which dd`)' \
-o test-safe-store.sdt \
-P ssh://`whoami`@localhost${PWD}/test-safe-store.pwd
# check files the standard way
$ python3 signduterre.py -e --passphrase ssh://`whoami`@localhost${PWD}/test-safe-store.pwd --check-file test-safe-store.sdt
# Check files using ssh on localhost
$ python3 signduterre.py -e --passphrase ssh://`whoami`@localhost${PWD}/test-safe-store.pwd --check-file test-safe-store.sdt \
--file-source ssh://`whoami`@localhost

Examples:

# make: example2
# Self test of root directory, python, and signduterre.py using the 'which' command to establish the paths
$ python3 signduterre.py --detailed-view --salt 436a73e3 --passphrase liauwefa3251EWC -o test-self.sdt \
 / `which python3 signduterre.py`
$ python3 signduterre.py --passphrase liauwefa3251EWC -c test-self.sdt

Write a signature to the file test-self.sdt and test it with the --check-file option. The signature contains the SHA-256 hashes of the files, /usr/bin/python3, signduterre.py, and the status information on the root directory. The salt '436a73e3' and passphrase 'liauwefa3251EWC' are used.

# make: procfs1
# Self test of root directory, python, and signduterre.py using the the /proc file system
$ python3 signduterre.py --detailed-view --salt SUGGESTED --passphrase liauwefa3251EWC -o test-self_proc.sdt \
 /proc/self/root /proc/self/exe `which signduterre.py`
$ python3 signduterre.py --passphrase liauwefa3251EWC --check-file test-self_proc.sdt

Write a signature to the file test-self_proc.sdt and test it with the --check-file option. The signature contains the SHA-256 hashes of the same files as above, /usr/bin/python3, signduterre.py, and the status information on the root directory. However, the python executable and the root directory are now accessed through the /proc file system. The suggested salt is used (written to test-self_proc.sdt) and the passphrase is (again) 'liauwefa3251EWC'.

# make: example3
# Test of supporting commands for chkrootkit
$ python3 signduterre.py --execute --total-only --salt SUGGESTED=8 --passphrase SUGGESTED --Status \
  --output-file=test-chkrootkit.sdt --Private-file=test-chkrootkit.pwd \
  signduterre.py `which bash awk cut egrep find head id ls netstat ps strings sed uname`
$ python3 signduterre.py --execute --passphrase test-chkrootkit.pwd --check-file test-chkrootkit.sdt

Writes a signature of the requested files to test-chkrootkit.sdt (signature) and private information to test-chkrootkit.pwd (password and selected salt) and checks it in the next line. The files are those of commands required by the chkrootkit program (http://www.chkrootkit.org/), with bash added. The 'which' command will give the paths for the commands. Eight salts are generated, of which only 1 is actually used. When checking, the correct salt should match. This prevents a compromised program from simply printing out OK tot he check. A more comprehensive evation of guessing the correct salt can be obtained by using the '--all-salts-pattern' option.

# make: procfs2
# Simply lump all "system" files, the PATH environment variable and the first 2 columns of the output of lsmod
$ python3 signduterre.py --execute --detail --salt SUGGESTED --passphrase liauwefa3251EWC --Status --total-only \
  signduterre.py /sbin/* /bin/* /usr/bin/find /usr/bin/stat /usr/bin/python3 '${PATH}' \
  '$(lsmod | awk "{print \$1, \$2}")' > test-20090625_14-31-54.sdt
# 
# Failing check due to missing --execute option
$ python3 signduterre.py --passphrase liauwefa3251EWC -c test-20090625_14-31-54.sdt
$ python3 signduterre.py --passphrase liauwefa3251EWC -c test-20090625_14-31-54.sdt --no-execute
# 
# Successful check
$ python3 signduterre.py --execute --passphrase liauwefa3251EWC --check-file test-20090625_14-31-54.sdt

Prints a signature to the system test-20090625_14-31-54.sdt and the automatically generated password to test-20090625_14-31-54.pwd. The salt will be automatically determined. The signature contains the SHA-256 hashes of the file status and file contents of signduterre.py, /sbin/*, /bin/*, /usr/bin/find, /usr/bin/file, /usr/bin/python* on separate lines, and a hash of the PATH environment variable. Do not display the hash of every single file, which could be insecure, but only the total hash. The first two checks will both fail if test-20090625_14-31-54.sdt contains a $(cmd) entry. The --no-execute option is default and prevents the execute option (if reading the execute option from the signature file has been activated). The last check will succeed (if the files have not been changed).

# make: example4
# Use a list of generated passphrases
$ python3 signduterre.py --salt SUGGESTED --passphrase SUGGESTED=20 signduterre.py \
2> test-20090630_16-44-34.pwd > test-20090630_16-44-34.sdt
$ python3 signduterre.py -p test-20090630_16-44-34.pwd -c test-20090630_16-44-34.sdt

Will generate and print 20 passphrases and print a signature using one randomly chosen passphrase from these 20. Everything is written to the files 'test-20090630_16-44-34.pwd' and 'test-20090630_16-44-34.sdt'. Such file names can easily be generated with 'test-`date "+%Y%m%d_%H-%M-%S"`.sdt'. The next command will check all 20 passphrases generated before from the Signature file and print the results.

# make: example5
# Use a list of generated salts with a pattern of correct salts
$ python3 signduterre.py --salt SUGGESTED=16 --passphrase SUGGESTED --all-salts-pattern \
-P test-salt-pattern.pwd -o test-salt-pattern.sdt `which bash stat find ls ps id uname awk gawk perl` 
$ python3 signduterre.py -p test-salt-pattern.pwd -c test-salt-pattern.sdt
# Compare to salt pattern number to the one from the check-file
$ cat test-salt-pattern.pwd

As the previous, but with a pattern of random correct and incorrect salts. The salt pattern number indicates which salts were and were not correct.

# make: sudo1
# Check MBR and current root directory (sudo and root user)
$ sudo python3 signduterre.py -u root -s SUGGESTED -p SUGGESTED --Status-values='i' -v -e -t \
--output-file test-boot-sector.sdt --Private-file test-boot-sector.pwd --execute-args=sda \
'?/proc/self/root' `which dd` '$(dd if=/dev/$1 bs=512 count=1 | od -X)'
$ sudo python3 signduterre.py -u root -e -p test-boot-sector.pwd -c test-boot-sector.sdt

Will hash the inode numbers of the effective root directory (eg, chroot) and the executable (python) together with the contents of the MBR (Master Boot Record) on /dev/sda in Hex. It uses suggested salt and passphrase. Accessing /dev/sda is only possible when root, so the command is entered with sudo and '--user root'. Use the '--print-execute' option if you want to check the output of the dd command.

The main problem with intrusion detection by comparing file contents is the ability of an attacker to redirect attempts to read a compromised file to a stored copy of the original. So, sha256sum or python could be changed to read '/home/attacker/old/ps' when the argument was '/bin/ps'. This would foil any scheme that depends on entering file names in programs. An answer to this threat is to read the bytes in files in as many ways as possible. Therefor, forcing an attacker to change many files which itself would increase the probability of detection of the attack. The following command will read the same (test) file, and generate identical hashes, in many different ways.

# make: example6
# Example generating identical signatures of the same text file in different ways
$ dd if=signduterre.py 2>/dev/null | \
python3 signduterre.py -v -d -s 1234567890abcdef -p poiuytrewq \
--execute --execute-args='signduterre.py' \
signduterre.py - \
'$(cat $1)' \
'$(grep "" $1)' \
'$(awk "{print}" $1)' \
'$(cut -f 1-100 $1)' \
'$(perl -ane "{print \$_}" $1)' \
'$(python3 -c "import sys;f=open(sys.argv[1]);sys.stdout.buffer.write(f.buffer.read())" $1;)' \
'$(ruby -e "f=open(ARGV[0]);print f.read();" $1;)'

These "commands" do not always return the same bytes (awk), or any bytes at all (grep), from a text file as when used with a binary file. However, if the commands can print the bytes unaltered, the signatures will be identical. That is, the following arguments will work on a binary file:

# make: example6
# Example generating identical signatures of the same file in different ways, now for binary files
$ dd if=/bin/bash 2>/dev/null | \
python3 signduterre.py -v -d -s 1234567890abcdef -p poiuytrewq \
--execute --execute-args='/bin/bash' \
/bin/bash - \
'$(cat $1)' \
'$(perl -ane "{print \$_}" $1)' \
'$(python3 -c "import sys;f=open(sys.argv[1]);sys.stdout.buffer.write(f.buffer.read())" $1;)' \
'$(ruby -e "f=open(ARGV[0]);print f.read();" $1;)'

Will generate the same identical signatures for /bin/bash, STDIN, '$(cat /bin/bash)' etc. There are obviously many more ways to read out the bytes from the disk or memory. The main point being that it should be difficult to predict for an attacker which commands must be compromised to hide changes in the system.

In case of a real compromised system, it is conceivable that the signatures will need to be checked using known good statically linked programs, eg, cat or dd from a cyptographically secured container like ecryptfs or an encrypted loopback device. An existing signature can be tested against such statically linked programs using the "--file-source '$(<cmd>)'" option. In this option, the plain file path will be substituted for every occurence of the string '{}' in the command. If no '{}' is present in the command, the file will simply be appended to the command. So, '$(/bin/dd if=)' is equivalent to '$(/bin/dd if={})' and '$(/bin/cat )' is equivalent to '$(/bin/cat {})'. Note the trailing space in '$(/bin/cat )'.

# make: example7
# Create standard signature
$ python3 signduterre.py --passphrase SUGGESTED --salt SUGGESTED --detailed-view --verbose \
`which python3 bash ps ls find stat lsof` 2> test-20090825_14_48-23.pwd > test-20090825_14_48-23.sdt
# Standard check
$ python3 signduterre.py --passphrase test-20090825_14_48-23.pwd --check-file test-20090825_14_48-23.sdt -v
# Example generating identical signatures checking with --file-source $(dd if=)
$ python3 signduterre.py --passphrase test-20090825_14_48-23.pwd --check-file test-20090825_14_48-23.sdt -v \
--execute --file-source '$(dd if=)'
# Example generating identical signatures checking with --file-source $(cat ) (note the space between 'cat' and ')')
$ python3 signduterre.py --passphrase test-20090825_14_48-23.pwd --check-file test-20090825_14_48-23.sdt -v \
--execute --file-source '$(cat )'
# Example generating identical signatures checking with --file-source $(perl -e "{open(F, "<{}");print <F>;};)
$ python3 signduterre.py --passphrase test-20090825_14_48-23.pwd --check-file test-20090825_14_48-23.sdt -v \
--execute --file-source '$(perl -e "{open(F, \"<{}\");print <F>;};")'

The integrity of a running 'cat' command can be checked with module proc_PID that will create a signature of all files loaded with the 'cat' command from the information in the /proc/<pid>/maps file using the inode numbers (sudo only). Debugfs will read the blocks directly from the medium using the inode tables without using the filenames.

# make: sudo2
# Use module proc_PID to check the integrety of 'cat' and all libraries loaded with it
# Check out the workings of proc_PID.py with '$ python3 proc_PID.py'
# The actual output of the module used in the signature can be inspected with --print-textdump
$ sudo python3 signduterre.py -p poiuytrewq --salt SUGGESTED --detailed-view \
 --verbose --execute -u root -o test-proc_PID.sdt --import proc_PID \
'@(return proc_PID.paths("cat","inode"))' '@(return proc_PID.fileSHA("cat", mainprefix))' \
'@(return proc_PID.inodeSHA("cat", "", mainprefix))' '@(return proc_PID.mapsSHA("cat", mainprefix))'
# Check the results
$ sudo python3 signduterre.py -p poiuytrewq  --detailed-view --verbose --execute -u root \
 --check-file test-proc_PID.sdt --import proc_PID

Check only the Table of Contents of this file using a byte range slice

# make: example8
# Create standard signature
$ python3 signduterre.py --passphrase kdfuhgcriu --salt SUGGESTED --detailed-view --verbose \
'signduterre.py[0o7:+136:0xf]' -o test-slices.sdt -P test-slices.pwd --print-textdump
# Standard check
$ python3 signduterre.py --passphrase kdfuhgcriu --detailed-view --verbose \
-c test-slices.sdt  --print-textdump

The examples can be run as a makefile using make. Use one of the following commands:

# General examples, use them all
python3 signduterre.py --manual-make |make -f - example
# Linux specific examples using the second procfs example
python3 signduterre.py --manual-make |make -f - procfs2
# Examples requiring sudo, using first
python3 signduterre.py --manual-make | sudo make -f - sudo1

Known Bugs:

- Reading files from STDIN (-) does not work if ssh:// has been used before as input for, eg, file arguments, --check-file or --passphrase

# make: sshbug1
# '-' stdin before ssh:// is fine
$ dd if=/bin/ps 2>/dev/null | python3.0 signduterre.py -edv -p SUGGESTED -s SUGGESTED /bin/ps - ssh://`whoami`@localhost/bin/ps 
# '-' stdin after ssh:// FAILs
$ dd if=/bin/ps 2>/dev/null | python3.0 signduterre.py -edv -p SUGGESTED -s SUGGESTED /bin/ps ssh://`whoami`@localhost/bin/ps -

- Reading URLs as file arguments should work when Python treats URLs identical to file descriptors. For the technically inclined: when:
with urllib.request.urlopen(url) as f:
works, URLs can be entered where ever file paths can be entered..