# Program: Signature-du-Terroir version 0.4 # 2009/07/31 11:41:17 (CET)
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
  -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
                        'fmidlugs' (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)
  --print-execute       Print the results of $() command execution to STDERR
                        for debugging purposes
   -m, --manual          Print 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).

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.

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 status=noxfer | 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

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.

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 enetered 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:

#
# Simple system sanity test using the 'which' command to establish the paths
$ python3.0 signduterre.py --passphrase SUGGESTED --salt SUGGESTED --detailed-view \
`which python3.0 bash ps ls find stat` 2> test-20090630_11-14-03.pwd > test-20090630_11-14-03.sdt
$ python3.0 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.0, bash, ps, ls, find, and stat. These files are found using the 'which' command.

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

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.

Examples:

# 
# Self test of root directory, python, and signduterre.py using the 'which' command to establish the paths
$ python3.0 signduterre.py --detailed-view --salt 436a73e3 --passphrase liauwefa3251EWC -o test-self.sdt \
 / `which python3.0 signduterre.py`
$ python3.0 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.0, signduterre.py, and the status information on the root directory. The salt '436a73e3' and passphrase 'liauwefa3251EWC' are used.

# 
# Self test of root directory, python, and signduterre.py using the the /proc file system
$ python3.0 signduterre.py --detailed-view --salt SUGGESTED --passphrase liauwefa3251EWC -o test-self_proc.sdt \
 /proc/self/root /proc/self/exe `which signduterre.py`
$ python3.0 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.0, 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'.

# 
# Test of supporting commands for chkrootkit
$ python3.0 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.0 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.

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

# 
# Use a list of generated passphrases
$ python3.0 signduterre.py --salt SUGGESTED --passphrase SUGGESTED=20 signduterre.py \
2> test-20090630_16-44-34.pwd > test-20090630_16-44-34.sdt
$ python3.0 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.

# 
# Use a list of generated salts with a pattern of correct salts
$ python3.0 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.0 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

# 
# Check MBR and current root directory (sudo and root user)
$ sudo python3.0 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 status=noxfer | od -X)'
$ sudo python3.0 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, sha256 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 7 different ways.

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

These seven "commands" do not return the same bytes (awk), or any bytes at all (grep), from the file when used with a binary file. Especially the conversion of binary data read internally differs from that returned by the $(cmd) interface. That is, the arguments "python3.0 signduterre.py -e -d ... /bin/ps '$(cat /bin/ps)'" will generate different signatures for /bin/ps and '$(cat /bin/ps)'.

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

# General examples
python3.0 signduterre.py --manual-make |make -f - example
# Linux specific examples
python3.0 signduterre.py --manual-make |make -f - linux
# Examples requiring sudo
python3.0 signduterre.py --manual-make | sudo make -f - sudo