\a
to generate unpleasant "beeps" from system unit speakers. This was especially inconvenient if you wanted to generate more complex sound sequences like 8-bit music. Therefore, Jonathan Nightingale wrote the program beep
. It was a short and very simple program that allowed fine-tuning the sound from the speaker.beep
to work, the user had to either be the superuser or own the current tty. That is, beep
will always work for a root user or for any local user, but will not work for a non-root remote user. At the same time, any terminal (for example, xterm) connected to the X-server is considered “remote”, and therefore beep
will not work.SUID
bit. This is a special bit, if you set it for a binary, the file is executed with the owner's rights (in this case, root), and not the ordinary user (yours).poweroff
to work, root privileges are needed (only a root user can shut down the computer), but for a personal computer this would be too much. Imagine that you are a sysadmin, and all users in the company ask you to turn off their computers. On the other hand, if one attacker can turn off a server with a large number of users, this is a serious security breach.SUID
are potential gaps. Take the same bash, a free root shell. Therefore, such programs are very carefully analyzed by the community.beep
, consisting of only 375 lines of code, scanned by a bunch of people, can be set up safely, despite the SUID
, right?beep
, it lies here: https://github.com/johnath/beep/blob/master/beep.c .play_beep()
. int main(int argc, char **argv) { /* ... */ signal(SIGINT, handle_signal); signal(SIGTERM, handle_signal); parse_command_line(argc, argv, parms); while(parms) { beep_parms_t *next = parms->next; if(parms->stdin_beep) { /* ... */ } else { play_beep(*parms); } /* Junk each parms struct after playing it */ free(parms); parms = next; } if(console_device) free(console_device); return EXIT_SUCCESS; }
play_beep()
opens the target device, searches for its types and calls do_beep()
for each repeat. void play_beep(beep_parms_t parms) { /* ... */ /* try to snag the console */ if(console_device) console_fd = open(console_device, O_WRONLY); else if((console_fd = open("/dev/tty0", O_WRONLY)) == -1) console_fd = open("/dev/vc/0", O_WRONLY); if(console_fd == -1) { /* ... */ } if (ioctl(console_fd, EVIOCGSND(0)) != -1) console_type = BEEP_TYPE_EVDEV; else console_type = BEEP_TYPE_CONSOLE; /* Beep */ for (i = 0; i < parms.reps; i++) { /* start beep */ do_beep(parms.freq); usleep(1000*parms.length); /* wait... */ do_beep(0); /* stop beep */ if(parms.end_delay || (i+1 < parms.reps)) usleep(1000*parms.delay); /* wait... */ } /* repeat. */ close(console_fd); }
do_beep()
simply calls the desired function to generate a signal depending on the target device: void do_beep(int freq) { int period = (freq != 0 ? (int)(CLOCK_TICK_RATE/freq) : freq); if(console_type == BEEP_TYPE_CONSOLE) { if(ioctl(console_fd, KIOCSOUND, period) < 0) { putchar('\a'); perror("ioctl"); } } else { /* BEEP_TYPE_EVDEV */ struct input_event e; e.type = EV_SND; e.code = SND_TONE; e.value = freq; if(write(console_fd, &e, sizeof(struct input_event)) < 0) { putchar('\a'); /* See above */ perror("write"); } } }
char *
), and if it worked, it interrupts the sound by calling do_beep(0)
. /* If we get interrupted, it would be nice to not leave the speaker beeping in perpetuity. */ void handle_signal(int signum) { if(console_device) free(console_device); switch(signum) { case SIGINT: case SIGTERM: if(console_fd >= 0) { /* Kill the sound, quit gracefully */ do_beep(0); close(console_fd); exit(signum); } else { /* Just quit gracefully */ exit(signum); } } }
SIGINT
and SIGTERM
sent at the same time, it is likely to call free()
twice. But I do not see any other useful applications besides the crash of the program, since after that the console_device
will not be used anywhere.write()
function in do_beep()
looks appropriate. It would be great to use it to write to an intermediate file!console_type
, which should be BEEP_TYPE_EVDEV
.console_type
is set in play_beep()
depending on the ioctl()
return value. That is, ioctl()
must allow BEEP_TYPE_EVDEV
.ioctl()
lie. If the file does not belong to the device, ioctl()
fails, device_type
will not be BEEP_TYPE_EVDEV
, and do_beep()
will not call write()
(instead, it uses ioctl()
, which, to my knowledge, is safe in this context).do_beep()
. If at this point in console_fd
and console_type
we have the correct values, then we will be able to write to the target file.play_beep()
? Here is the code: void play_beep(beep_parms_t parms) { /* ... */ /* try to snag the console */ if(console_device) console_fd = open(console_device, O_WRONLY); else if((console_fd = open("/dev/tty0", O_WRONLY)) == -1) console_fd = open("/dev/vc/0", O_WRONLY); if(console_fd == -1) { /* ... */ } if (ioctl(console_fd, EVIOCGSND(0)) != -1) console_type = BEEP_TYPE_EVDEV; else console_type = BEEP_TYPE_CONSOLE; /* Beep */ for (i = 0; i < parms.reps; i++) { /* start beep */ do_beep(parms.freq); usleep(1000*parms.length); /* wait... */ do_beep(0); /* stop beep */ if(parms.end_delay || (i+1 < parms.reps)) usleep(1000*parms.delay); /* wait... */ } /* repeat. */ close(console_fd); }
beep
. If the previous call is successful, console_fd
and console_type
will still have their old values.console_fd
has a new value, and console_type
still has the old value.console_device
). But you can make a symlink, first leading to the correct device, and then to the target file. struct input_event e; e.type = EV_SND; e.code = SND_TONE; e.value = freq; if(write(console_fd, &e, sizeof(struct input_event)) < 0) { putchar('\a'); /* See above */ perror("write"); }
struct input_event
defined in linux/input.h
: struct input_event { struct timeval time; __u16 type; __u16 code; __s32 value; }; struct timeval { __kernel_time_t tv_sec; /* seconds */ __kernel_suseconds_t tv_usec; /* microseconds */ }; // On my system, sizeof(struct timeval) is 16.
time
element is not assigned to the beep
source code, and this is the first element of the structure, so its value will be the first bytes of the target file after the attack.-l
parameter will be stored there, and after that - \0
. The value is integer, which gives us 4 bytes./*/x
. In a shell script, this will execute the program (pre-made) /tmp/x
./etc/profile
or /etc/bash/bashrc
, then we will achieve complete success with any logged in user./dev/input/event0
, starts beep
, waits a bit, reassigns the link, waits again, and then generates a signal. $ echo 'echo PWND $(whoami)' > /tmp/x $ ./exploit.py /etc/bash/bashrc # Or any shell script Backup made at '/etc/bash/bashrc.bak' Done! $ su PWND root
Source: https://habr.com/ru/post/354130/
All Articles