#c
Вопрос:
Я читаю книгу «Хакерство: искусство эксплуатации». Есть небольшая программа, которая читает /var/notes, которая является двоичным файлом, сгенерированным мной, для поиска и вывода некоторых строк. Тем не менее, функция чтения в функции print_notes всегда возвращает 0, и буфера note_buffer вообще не затрагивается. Итак, каждый раз, когда я получаю какой-то нечитаемый вывод с консоли, такой как этот:
$ ./notesearch [DEBUG] found a 10 byte note for user id 1000 �w}�-------[ end of note data ]-------
Содержание /var/примечаний:
$ sudo hexdump -C /var/notes 00000000 e8 03 00 00 0a 61 61 61 62 62 62 63 63 63 0a |.....aaabbbccc.| 0000000f
Первые 4 байта обозначают целое число (в данном случае 1000), а остальные-это просто код ASCII.
Эта программа предназначена для изучения setuid, поэтому разрешения на файлы устанавливаются, как показано ниже:
$ ll ./notesearch /var/notes -rwsrwxr-x 1 root root 22K Nov 19 22:23 ./notesearch -rw------- 1 root root 15 Nov 19 22:22 /var/notes
Кто-нибудь знает, почему функция чтения всегда возвращает 0? Кстати, я использую Ubuntu 20.04 и GCC 9.3.
Вот код:
#include lt;stdio.hgt; #include lt;string.hgt; #include lt;fcntl.hgt; #include lt;sys/stat.hgt; #include "hacking.h" #define FILENAME "/var/notes" int print_notes(int, int, char *); // note printing function int find_user_note(int, int); // seek in file for a note for user int search_note(char *, char *); // search for keyword function void fatal(char *); // fatal error handler int main(int argc, char *argv[]) { int userid, printing=1, fd; // file descriptor char searchstring[100]; if(argc gt; 1) // If there is an arg strcpy(searchstring, argv[1]); // that is the search string else // otherwise searchstring[0] = 0; // search string is empty userid = getuid(); fd = open(FILENAME, O_RDONLY); // open the file for read-only access 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); } // A function to print the notes for a given uid that match // an optional search string // returns 0 at end of file, 1 if there are still more notes 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); if(note_length == -1) // if end of file reached return 0; // return 0 read(fd, note_buffer, note_length); // read note data note_buffer[note_length] = 0; // terminate the string if(search_note(note_buffer, searchstring)) // if searchstring found printf(note_buffer); // print the note return 1; } // A function to find the next note for a given userID // returns -1 if the end of the file is reached // otherwise it returns the length of the found note int find_user_note(int fd, int user_uid) { int note_uid=-1; unsigned char byte; int length; while(note_uid != user_uid) { // loop until a note for user_uid is found if(read(fd, amp;note_uid, 4) != 4) // read the uid data return -1; // if 4 bytes aren't read, return end of file code if(read(fd, amp;byte, 1) != 1) // read the newline separator return -1; byte = length = 0; while(byte != 'n') { // figure out how many bytes to the end of line if(read(fd, amp;byte, 1) != 1) // read a single byte return -1; // if byte isn't read, return end of file code length ; } } lseek(fd, length * -1, SEEK_CUR); // rewind file reading by length bytes printf("[DEBUG] found a %d byte note for user id %dn", length, note_uid); return length; } // A function to search a note for a given keyword // returns 1 if a match is found, 0 if there is no match int search_note(char *note, char *keyword) { int i, keyword_length, match=0; keyword_length = strlen(keyword); if(keyword_length == 0) // if there is no search string return 1; // always "match" for(i=0; i lt; strlen(note); i ) { // iterate over bytes in note if(note[i] == keyword[match]) // if byte matches keyword match ; // get ready to check the next byte else { // otherwise if(note[i] == keyword[0]) // if that byte matches first keyword byte match = 1; // start the match count at 1 else match = 0; // otherwise it is zero } if(match == keyword_length) // if there is a full match return 1; // return matched } return 0; // return not matched }
Комментарии:
1. Ваш двоичный файл очень любопытен. Зачем вам двоичное целое число в файле с символами новой строки? Вы просто используете их для разделителей? Ваш отладочный вывод отличается от одного, так как у вас есть 9 байтов в примечании между разделителями (например
"aaabbbccc"
), что"[DEBUG] found a 10 byte note ..."
выглядит немного странно. Похоже, вы хотитеwhile (read(fd, amp;byte, 1) == 1) { ... }
, а не заполняетеbyte
и сравниваете с'n'
условием. Вероятно, есть и другие проблемы, но это выделялось.
Ответ №1:
После долгих поисков я наконец нахожу причину. Итак, мне не хватает #include lt;unistd.hgt;, если я включу файл заголовка, все будет работать нормально.
Я продолжил поиск причин, по которым этот код может компилироваться и запускаться без включения заголовка. Оказывается, что если GCC видит функцию без объявления, он делает некоторые предположения относительно функции (одно из них возвращает значение int). Однако, если истинное определение функции отличается от предположения, программа будет иметь неопределенное поведение. В этом случае истинным объявлением для lseek является
extern __off_t lseek (int __fd, __off_t __offset, int __whence) __THROW;
но компилятор предполагает, что объявление будет
int lseek(...)
Таким образом, программа ведет себя неопределенно, поскольку она возвращает содержимое из регистра, в котором обычно хранится возвращаемое значение int.
Кстати, программа может передать компоновщик, потому что в стандартной библиотеке есть функция под названием lseek, которая соответствует вызову в print_notes.
Комментарии:
1. Если это так, разве вы не получили предупреждение «неявное объявление»? Это для того, чтобы предупредить вас именно об этом случае; это очень серьезное предупреждение, которое не следует игнорировать. (Хотя, если на то пошло, также не следует предупреждать компилятор, пока вы не докажете, что он безвреден).
2. @NateEldridge Я действительно получил несколько предупреждений, но я просто проигнорировал их. Я ожидал, что код не сможет скомпилироваться, если произойдет такое пропущенное объявление, так как я больше привык к c .
3. Что ж, тогда, надеюсь, урок усвоен. Пропущенные декларации были незаконны в C начиная с C99, но стандарт требует только «диагностики», которая не обязательно должна быть фатальной. Возможно, к сожалению, многие компиляторы, включая GCC, просто выдадут предупреждение и вернутся к неявному поведению int в C89. Поэтому вам нужно следить за предупреждениями — и, конечно, есть множество других предупреждений, которые компилятор может выдавать для кода, который является совершенно законным C, но не тем, что вам нужно. Многие люди советуют использовать
-Wall -Werror
так, чтобы предупреждения становились фатальными и вы вообще не могли их игнорировать.