110
0x200 Программирование
#include
#include
#include
#include “hacking.h”
void usage(char *prog_name, char *filename) {
printf(“Usage: %s \n”, prog_name, filename);
exit(0);
}
void fatal(char *); // Функция обработки критических ошибок void *ec_malloc(unsigned int); // Оболочка для malloc() с проверкой ошибок int main(int argc, char *argv[]) {
int userid, fd; // File descriptor
char *buffer, *datafile;
buffer = (char *) ec_malloc(100);
datafile = (char *) ec_malloc(20);
strcpy(datafile, “/var/notes”);
if(argc < 2) // Если аргументов командной строки нет,
usage(argv[0], datafile); // вывести сообщение о правилах вызова
// и завершить работу.
strcpy(buffer, argv[1]); // Копировать в буфер.
printf(“[DEBUG] buffer @ %p: \’%s\’\n”, buffer, buffer);
printf(“[DEBUG] datafile @ %p: \’%s\’\n”, datafile, datafile);
// Открытие файла fd = open(datafile, O_WRONLY|O_CREAT|O_APPEND, S_IRUSR|S_IWUSR);
if(fd == -1)
fatal(“in main() while opening file”);
printf(“[DEBUG] file descriptor is %d\n”, fd);
userid = getuid(); // Получить фактический ID пользователя.
// Запись данных
if(write(fd, &userid, 4) == -1) // Записать ID пользователя перед данными.
fatal(«in main() while writing userid to file»);
write(fd, «\n», 1); // Добавить перевод строки.
if(write(fd, buffer, strlen(buffer)) == -1) // Записать заметку.
fatal(“in main() while writing buffer to file”);
write(fd, “\n”, 1); // Добавить перевод строки.
// Закрытие файла if(close(fd) == -1)
fatal(“in main() while closing file”);
0x280 Опираясь на основы
111 printf(“Note has been saved.\n”);
free(buffer);
free(datafile);
}
Файлом для вывода теперь служит не
/var/notes, а
/tmp/notes, чтобы данные хранились в более надежном месте. Для получения фактиче- ского идентификатора пользователя применяется функция getuid().
ID
записывается в файл данных перед тем, как в него выводится строка заметки. Поскольку функция write() принимает в качестве аргумента указатель на источник, с помощью оператора & получаем адрес целого числа userid.
reader@hacking:/booksrc $ gcc -o notetaker notetaker.c reader@hacking:/booksrc $ sudo chown root:root ./notetaker reader@hacking:/booksrc $ sudo chmod u+s ./notetaker reader@hacking:/booksrc $ ls -l ./notetaker
-rwsr-xr-x 1 root root 9015 2007-09-07 05:48 ./notetaker reader@hacking:/booksrc $ ./notetaker “this is a test of multiuser notes”
[DEBUG] buffer @ 0x804a008: ‘this is a test of multiuser notes’
[DEBUG] datafile @ 0x804a070: ‘/var/notes’
[DEBUG] file descriptor is 3
Note has been saved.
reader@hacking:/booksrc $ ls -l /var/notes
-rw------- 1 root reader 39 2007-09-07 05:49 /var/notes reader@hacking:/booksrc $
Здесь показан результат компиляции программы
notetaker, изменения ее владельца на root и установки флага setuid. Теперь при запуске про- граммы она работает от имени пользователя root, поэтому владельцем файла
/var/notes, когда он будет создан, также станет root.
reader@hacking:/booksrc $ cat /var/notes cat: /var/notes: Permission denied reader@hacking:/booksrc $ sudo cat /var/notes
?
this is a test of multiuser notes reader@hacking:/booksrc $ sudo hexdump -C /var/notes
00000000
e7 03 00 00 0a 74 68 69 73 20 69 73 20 61 20 74 |.....this is a t|
00000010 65 73 74 20 6f 66 20 6d 75 6c 74 69 75 73 65 72 |est of multiuser|
00000020 20 6e 6f 74 65 73 0a | notes.|
00000027
reader@hacking:/booksrc $ pcalc 0x03e7 999 0x3e7 0y1111100111
reader@hacking:/booksrc $
В файле
/var/notes записаны ID пользователя reader (999) и заметка.
Поскольку в данной архитектуре установлен порядок байтов «сначала младший», 4 байта целого числа 999 при выводе в шестнадцатеричном виде появляются в обратном порядке (выделены полужирным).
1120x200 Программирование
Чтобы обычный пользователь мог читать заметки из файла, ему нужна программа с флагом setuid root. Программа
notesearch.c будет читать файл заметок и отображать только заметки, записанные пользовате- лем, у которого тот же ID. Кроме того, появился необязательный аргу- мент командной строки, задающий строку для поиска. Если он задан, то отображаются только те заметки, где есть искомая строка.
notesearch.c#include
#include
#include
#include
#include “hacking.h”
#define FILENAME “/var/notes”
int print_notes(int, int, char *); // Функция для вывода заметок.
int find_user_note(int, int); // Поиск заметки пользователя
// в файле.
int search_note(char *, char *); // Функция поиска ключевого слова.
void fatal(char *); // Обработчик критических ошибок.
int main(int argc, char *argv[]) {
int userid, printing=1, fd; // Дескриптор файла.
char searchstring[100];
if(argc > 1) // Если есть аргумент,
strcpy(searchstring, argv[1]); // это строка для поиска;
else // в противном случае searchstring[0] = 0; // строка для поиска пуста.
userid = getuid();
fd = open(FILENAME, O_RDONLY); // Открыть файл только для чтения.
if(fd == -1)
fatal(“in main() while opening file for reading”);
while(printing)
printing = print_notes(fd, userid, searchstring);
printf(“-------[ end of note data ]-------\n”);
close(fd);
}
// Функция для вывода заметок с заданным ID пользователя,
// соответствующих строке поиска, если она задана;
// возвращает 0, если достигнут конец файла, и 1, если есть другие заметки.
int print_notes(int fd, int uid, char *searchstring) {
int note_length;
char byte=0, note_buffer[100];
note_length = find_user_note(fd, uid);
0x280 Опираясь на основы
113 if(note_length == -1) // Если достигнут конец файла,
return 0; // вернуть 0.
read(fd, note_buffer, note_length); // Прочитать данные заметки.
note_buffer[note_length] = 0; // Символ конца строки.
if(search_note(note_buffer, searchstring)) // Если строка найдена,
printf(note_buffer); // вывести заметку.
return 1;
}
// Функция для поиска следующей заметки с заданным ID пользователя;
// возвращает -1, если достигнут конец файла;
// в противном случае возвращает длину найденной заметки.
int find_user_note(int fd, int user_uid) {
int note_uid=-1;
unsigned char byte;
int length;
while(note_uid != user_uid) { // Повторять цикл,
// пока не найдется user_uid.
if(read(fd, ¬e_uid, 4) != 4) // Прочитать данные для uid.
return -1; // Если 4 байта не прочитаны, вернуть код конца файла.
if(read(fd, &byte, 1) != 1) // Прочитать символ перевода строки.
return -1;
byte = length = 0;
while(byte != ‘\n’) { // Узнать, сколько байт осталось
// до конца строки.
if(read(fd, &byte, 1) != 1) // Прочитать один байт.
return -1; //
Если не удалось прочитать байт,
// вернуть код конца файла.
length++;
}
}
lseek(fd, length * -1, SEEK_CUR); // Переместить текущую позицию чтения
// назад на length байт.
printf(“[DEBUG] found a %d byte note for user id %d\n”, length, note_uid);
return length;
}
// Функция для поиска заметки по данному ключевому слову;
// возвращает 1, если заметка найдена, и 0, если не найдена.
int search_note(char *note, char *keyword) {
int i, keyword_length, match=0;
keyword_length = strlen(keyword);
if(keyword_length == 0) // Если строка поиска не задана,
return 1; // всегда “совпадение”.
1140x200 Программирование for(i=0; i < strlen(note); i++) { // Цикл по всем байтам заметки.
if(note[i] == keyword[match]) // Если совпадает с байтом
// ключевого слова,
match++; // приготовиться к проверке следующего байта;
else { // в противном случае if(note[i] == keyword[0]) // если байт совпадает с первым байтом
// ключевого слова,
match = 1; // в счетчик повторений записывается 1.
else match = 0; // В противном случае - 0.
}
if(match == keyword_length) // Если найдено полное совпадение,
return 1; // вернуть код совпадения.
}
return 0; // Вернуть код несовпадения.
}
В основном этот код должен быть ясен, но в нем есть ряд новых поня- тий. В начале определено имя файла, чтобы не размещать его в куче.
Далее функция lseek() используется для перемещения текущей пози- ции чтения из файла. Вызов lseek(fd, length * -1, SEEK_CUR);
сообщает программе, что позицию чтения файла нужно переместить вперед на length
* -1 байт. Поскольку это число отрицательное, позиция переме- щается назад на length байт.
reader@hacking:/booksrc $ gcc -o notesearch notesearch.c reader@hacking:/booksrc $ sudo chown root:root ./notesearch reader@hacking:/booksrc $ sudo chmod u+s ./notesearch reader@hacking:/booksrc $ ./notesearch
[DEBUG] found a 34 byte note for user id 999
this is a test of multiuser notes
-------[ end of note data ]------- reader@hacking:/booksrc $
После компиляции и установки флага setuid для root программа
notesearch работает так, как предполагалось. Но пока у нас только один пользователь – что будет, когда с программами
notetaker и
notesearch станет работать другой пользователь?
reader@hacking:/booksrc $ sudo su jose jose@hacking:/home/reader/booksrc $ ./notetaker “This is a note for jose”
[DEBUG] buffer @ 0x804a008: ‘This is a note for jose’
[DEBUG] datafile @ 0x804a070: ‘/var/notes’
[DEBUG] file descriptor is 3
Note has been saved.
jose@hacking:/home/reader/booksrc $ ./notesearch
[DEBUG] found a 24 byte note for user id 501
This is a note for jose
-------[ end of note data ]------- jose@hacking:/home/reader/booksrc $
0x280 Опираясь на основы
115Когда эти программы запустит пользователь jose, фактическим ID пользователя будет 501. Эта величина будет добавляться ко всем за- меткам, сделанным с помощью
notetaker, а программа
notesearch будет отображать только заметки с совпадающим ID пользователя.
reader@hacking:/booksrc $ ./notetaker “This is another note for the reader user”
[DEBUG] buffer @ 0x804a008: ‘This is another note for the reader user’
[DEBUG] datafile @ 0x804a070: ‘/var/notes’
[DEBUG] file descriptor is 3
Note has been saved.
reader@hacking:/booksrc $ ./notesearch
[DEBUG] found a 34 byte note for user id 999
this is a test of multiuser notes
[DEBUG] found a 41 byte note for user id 999
This is another note for the reader user
-------[ end of note data ]------- reader@hacking:/booksrc $
Аналогично все заметки пользователя reader помечены идентификато- ром 999. Несмотря на то что для обеих программ
notetaker и
notesearch установлен бит suid root и у них есть полный доступ на чтение и запись к файлу
/var/notes, логика программы
notesearch не дает запустивше- му ее пользователю просматривать заметки других авторов. Это очень напоминает то, как в файле
/etc/passwd хранятся данные обо всех поль- зователях, но программы, вроде chsh и passwd, разрешают любому поль- зователю изменить только свою оболочку или пароль.
0x284 СтруктурыИногда требуется объединить несколько переменных в группу и обра- щаться с ней как с одной переменной. В C есть понятие
структуры – пе- ременной, которая может хранить в себе другие переменные. Структу- ры часто используются в системных функциях и библиотеках, поэто- му для работы с этими функциями необходимо понимать структуры.
Рассмотрим простой пример. Многие функции,
работающие со време- нем, используют структуру, которая называется tm и определена в
/usr/include/time.h. Определена эта структура так:
struct tm {
int tm_sec; /* секунды */
int tm_min; /* минуты */
int tm_hour; /* часы */
int tm_mday; /* число месяца */
int tm_mon; /* месяц */
int tm_year; /* год */
int tm_wday; /* день недели */
int tm_yday; /* день года */
int tm_isdst; /* летнее время */
};
116
0x200 Программирование
После того как структура tm определена, она становится допустимым типом переменной и можно объявлять переменные и указатели с ти- пом данных tm. Это иллюстрирует программа time_example.c. После включения заголовочного файла time.h тип tm становится определен- ным и далее используется для объявления переменных current_time и time_ptr.
time_example.c
#include
#include
int main() {
long int seconds_since_epoch;
struct tm current_time, *time_ptr;
int hour, minute, second, day, month, year;
seconds_since_epoch = time(0); // Передать функции time в качестве аргумента нулевой указатель.
printf(“time() - seconds since epoch: %ld\n”, seconds_since_epoch);
time_ptr = ¤t_time; // Поместить в time_ptr адрес
// структуры current_time.
localtime_r(&seconds_since_epoch, time_ptr);
// Три способа доступа к элементам структуры:
hour = current_time.tm_hour; // Прямой доступ minute = time_ptr->tm_min; // Доступ по указателю second = *((int *) time_ptr); // Хакерский доступ по указателю printf(“Current time is: %02d:%02d:%02d\n”, hour, minute, second);
}
Функция time() возвращает количество секунд, прошедших с 1 января
1970 года. В UNIX-системах время отсчитывается относительно этого произвольно выбранного момента времени, называемого также нача-
лом эпохи (epoch). Функция localtime_r() принимает в качестве аргу- ментов два указателя: один – на количество секунд, прошедших с на- чала эпохи, другой – на структуру tm. Указатель time_ptr уже установ- лен на адрес current_time – незаполненной структуры tm. С помощью оператора адреса получается указатель на секунды с начала эпохи, принимаемый в качестве другого аргумента функции localtime_r(), ко- торая заполняет элементы tm. К элементам структур можно обращать- ся тремя разными способами: первые два допустимы при доступе к эле- ментам структуры, а третий является хакерским решением. Если пе- ременная имеет тип структуры, доступ к ее элементам осуществляет- ся путем дописывания имен элементов к имени переменной через точ- ку. Следовательно, current_time.tm_hour предоставляет доступ к эле- менту tm_hour структуры current_time типа tm. Часто прибегают к указа-
0x280 Опираясь на основы
117телям на структуры, потому что гораздо эффективнее
передать указа- тель размером в четыре байта, чем передавать всю структуру данных.
Указатели на структуру встречаются настолько часто, что в C включен встроенный метод для доступа к элементам структуры по указателю на нее, что устраняет необходимость разыменования. При использовании указателя на структуру вроде time_ptr можно также обращаться к эле- менту структуры по имени, но через символы, напоминающие стрелку вправо. Например, time_ptr->tm_min – адрес элемента tm_min структуры типа tm, указателем на которую служит time_ptr. К секундам (элемент tm_sec структуры типа tm) можно обратиться с помощью любого из этих двух законных методов, но в программе применен иной способ. Догада- етесь ли вы, как работает этот третий метод?
reader@hacking:/booksrc $ gcc time_example.c reader@hacking:/booksrc $ ./a.out time() - seconds since epoch: 1189311588
Current time is: 04:19:48
reader@hacking:/booksrc $ ./a.out time() - seconds since epoch: 1189311600
Current time is: 04:20:00
reader@hacking:/booksrc $
Программа работает так, как предполагалось, но каким же образом из структуры tm извлекаются секунды? Вспомним, что в конечном сче- те все сводится к памяти. Поскольку структура tm начинается с tm_sec, соответствующее целочисленное значение тоже находится в нача- ле. В строке second = *((int *) time_ptr) для переменной time_ptr вы- полняется приведение типа из указателя на структуру tm в указатель на целый тип. После этого приведенный указатель разыменовывает- ся и возвращает данные, находящиеся по адресу указателя. Так как адрес структуры tm является также адресом ее первого элемента, мы получим целое число – значение элемента tm_sec структуры. Дополне- ние, которое мы ввели в код
time_example.c (
time_example2.c), выводит также байты current_time. Из этого видно, что элементы tm примыкают друг к другу в памяти. Последующие элементы структуры также могут быть получены по указателю простым прибавлением соответствующе- го числа к адресу указателя.