Security Incidents mailing list archives

Re: Incident investigation methodologies


From: Valdis.Kletnieks () vt edu
Date: Fri, 04 Jun 2004 15:14:58 -0400

On Fri, 04 Jun 2004 10:42:46 CDT, Paul Schmehl <pauls () utdallas edu>  said:

For example, a statically compiled copy of ls on a CD is going to show you 
what's on the hard drive of a unix machine no matter what the rootkit may 
have done.

Umm.. No. ;)  A statically linked /bin/ls will protect you against a
trojaned libc.a, but it won't protect you against a kernel that's actively
lying to you.

Consider that every copy of /bin/ls I've encountered in 25 years basically
relied on things like readdir() and stat() returning the information requested.
 If the kernel returns bogus values on stat(), /bin/ls will report the bogus
values.

http://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=121292

for an actual real-life example of a bug caused by /bin/ls reporting the Wrong
Thing because a system call *fails* (in this case, it does a stat() call, which
fails, and it then blindly reports the file owner as 'root' because the
never-filled-in 'struct stat' happened to have st_uid == 0).

Now...  consider what happens to your statically-linked /bin/ls if it issues a
stat() system call, and this code (from the Linux 2.6.7-rc2-mm2 kernel fs/
inode.c):

void generic_fillattr(struct inode *inode, struct kstat *stat)
{
        stat->dev = inode->i_sb->s_dev;
        stat->ino = inode->i_ino;
        stat->mode = inode->i_mode;
        stat->nlink = inode->i_nlink;
        stat->uid = inode->i_uid;
        stat->gid = inode->i_gid;
        stat->rdev = inode->i_rdev;
        stat->atime = inode->i_atime;
        stat->mtime = inode->i_mtime;
        stat->ctime = inode->i_ctime;
        stat->size = i_size_read(inode);
        stat->blocks = inode->i_blocks;
        stat->blksize = inode->i_blksize;
}

is modified by the rootkit to do this instead:

static struct kstat fake_binsu = {...} /* initialized to pre-trojaned stat() of /bin/su */

void malicious_fillattr(struct inode *inode, struct kstat *stat)
{
        stat->dev = inode->i_sb->s_dev;
        stat->ino = inode->i_ino;
        stat->mode = inode->i_mode;
        stat->nlink = inode->i_nlink;
        stat->uid = inode->i_uid;
        stat->gid = inode->i_gid;
        stat->rdev = inode->i_rdev;
        stat->atime = inode->i_atime;
        stat->mtime = inode->i_mtime;
        stat->ctime = inode->i_ctime;
        stat->size = i_size_read(inode);
        stat->blocks = inode->i_blocks;
        stat->blksize = inode->i_blksize;
        if (current->uid != 666 && (stat->dev == fake_binsu.dev) &&
            (stat->ino == fake_binsu.ino)) {
                stat = fake_binsu;
        }
}

Hmm.. what do you know.. unless you're running as UID 666, doing a stat() on
/bin/su will report the old pre-trojaned size, mtime, atime, ctime, and permissions...
At that point, there's nothing ls can do about it.

If you have a program on that CD that opens the raw /dev/disk partition and
starts poking around the file system structure to find the actual inode out on
the oxide platter (a statically linked copy of the Linux ext2progs 'debugfs' or
the Solaris 'fsdb_ufs' or similar program appropriate for the file system)
would probably work well here), you might be in luck if the rootkit doesn't
fake the read() and write() calls for raw disk access (which none do as far as
I know). However, even that can fail - think about why there's a *reason*
you're not supposed to fsck a mounted partition...

Attachment: _bin
Description:


Current thread: