我正在读这本书"黑客:剥削的艺术;。有一个小程序可以读取/var/notes,这是我自己生成的一个二进制文件,用于搜索和输出一些字符串。但是,函数print_notes中的read函数总是返回0,并且note_buffer根本没有被触摸。所以,每次我从控制台得到一些不可读的输出时,就像这样:
$ ./notesearch
[DEBUG] found a 10 byte note for user id 1000
�w}�-------[ end of note data ]-------
/var/notes的内容为:
$ 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
有人知道为什么read函数总是返回0吗?顺便说一下,我使用的是Ubuntu 20.04和GCC 9.3。
这是代码:
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#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 > 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, ¬e_uid, 4) != 4) // read the uid data
return -1; // if 4 bytes aren't read, return end of file code
if(read(fd, &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, &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 < 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
}
经过一段时间的搜索,我终于找到了原因。所以,我错过了#include<unistd.h>,如果我包括头文件,一切都很好。
我进一步搜索了为什么这些代码可以在不包含头的情况下编译和运行。事实证明,如果GCC看到一个没有声明的函数,它会对该函数进行一些假设(其中之一是返回int(。然而,如果函数的真实定义与假设不同,则程序将具有未定义的行为。在这种情况下,lseek的真实声明是
extern __off_t lseek (int __fd, __off_t __offset, int __whence) __THROW;
但是编译器假定声明是
int lseek(...)
因此,程序具有未定义的行为,因为它将从通常存储int返回值的寄存器返回内容。
顺便说一句,程序可以传递链接器,因为有一个名为lseek的函数适合标准库中print_notes中的调用。