Example Using find.c Using find.c as an example, how would this manual source code auditing process work? We need to start with user data entering the program. As seen in the preceding ITS4 output, a recvfrom() function call accepts an incoming UDP packet. The code surrounding the call looks like this:
char buf[65536]; // buffer to receive incoming udp packet
int sock, pid; // socket descriptor and process id
sockaddr_in fsin; // internet socket address information
//...
// Code to take care of the socket setup
//...
while(1){ // loop forever
unsinged int alen = sizeof(fsin);
// now read the next incoming UPD packet
if(recvfrom(sock, buf, sizeof(buf), 0, (struct sockaddr *)&fsin, &alen) < 0){
// exit the program if an error occurred
errexit("recvfrom: %s\n", strerror(errno));
}
pid = fork(); // fork a child to process the packet
if(pid == 0){ // Then this must be the child
manage_request(buf, sock, &fsin); // child handles packet
exit(0); // child exits after packet is processed
}
}
The preceding code shows a parent process looping to receive incoming UDP packets using the recvfrom() function. Following a successful recvfrom(), a child process is forked and the manage_request() function is called to process the received packet. We need to trace into manage_request() to see what happens with the user’s input. We can see right off the bat that none of the parameters passed in to manage_request() deals with the size of buf, which should make the hair on the back of our necks stand up. The manage_request() function starts out with a number of data declarations, as shown here:
void manage_request(char *buf, int sock, struct sockaddr_in* addr){
char init_cwd[1024];
char cmd[512];
char outf[512];
char replybuf[65536];
char *user;
char *password;
char *filename;
char *keyword;
char *envstrings[16];
char *id;
char *field;
char *p;
int i;
Here, we see the declaration of many of the fixed-size buffers noted earlier by RATS. We know that the input parameter buf points to the incoming UDP packet, and the buffer may contain up to 65,535 bytes of data (the maximum size of a UDP packet). There are two interesting things to note here: First, the length of the packet is not passed into the function, so bounds checking will be difficult and perhaps completely dependent on well-formed packet content. Second, several of the local buffers are significantly smaller than 65,535 bytes, so the function had better be very careful how it copies information into those buffers. Earlier, it was mentioned that the buffer at line 172 is vulnerable to an overflow. That seems a little difficult given that there is a 64KB buffer sitting between it and the return address.
The function proceeds to set some of the pointers by parsing the incoming packet, which is expected to be formatted as follows:
id some_id_value\n
user some_user_name\n
password some_users_password\n
filename some_filename\n
keyword some_keyword\n
environ key=value key=value key=value ...\n
The pointers in the stack are set by locating the key name, searching for the following space, and incrementing by one character position. The values become null terminated when the trailing \n is located and replaced with \0. If the key names are not found in the order listed, or trailing \n characters fail to be found, the input is considered malformed and the function returns. Parsing the packet goes well until processing of the optional environ values begins. The environ field is processed by the following code (note, the pointer p at this point is positioned at the next character that needs parsing within the input buffer):
envstrings[0] = NULL; // assume no environment strings
if(!strncmp("environ", p, strlen("environ"))){
field = memchr(p, ' ', strlen(p)); // find trailing space
if(field == NULL){ // error if no trailing space
reply(id, "missing environment value", sock, addr);
return;
}
field++; // increment to first character of key
i = 0; // init our index counter into envstrings
while(1){ // loop as long as we need to
envstrings[i] = field; // save the next envstring ptr
p = memchr(field, ' ', strlen(field)); // trailing space
if(p==NULL){ // if no space then we need a newline
p = memchr(field, '\n', strlen(field));
if (p==NULL){
reply(id, "malformed environment value", sock, addr);
return;
}
}
*p = '\0'; // found newline terminate last envstring
i++; // count the envstring
break; // newline marks the end so break
}
*p = '\0'; // terminate the envstring
field = p + 1; // point to start of next envstring
i++; // count the envstring
}
envstrings[i] = NULL; // terminate the list
}
Following the processing of the environ field, each pointer in the envstrings array is passed to the putenv() function, so these strings are expected to be in the form key=value. In analyzing this code, note that the entire environ field is optional, but skipping it wouldn’t be any fun for us. The problem in the code results from the fact that the while loop that processes each new environment string fails to do any bounds checking on the counter i, but the declaration of envstrings only allocates space for 16 pointers. If more than 16 environment strings are provided, the variables below the envstrings array on the stack will start to get overwritten. We have the makings of a buffer overflow at this point, but the question becomes: “Can we reach the saved return address?” Performing some quick math tells us that there are about 67,600 bytes of stack space between the envstrings array and the saved frame pointer/saved return address. Because each member of the envstrings array occupies 4 bytes, if we add 67,600/4 = 16,900 additional environment strings to our input packet, the pointers to those strings will overwrite all of the stack space up to the saved frame pointer. Two additional environment strings will give us an overwrite of the frame pointer and the return address. How can we include 16,918 environment strings if the form key=value is in our packet? If a minimal environment string, say x=y, consumes 4 bytes counting the trailing space, then it would seem that our input packet needs to accommodate 67,672 bytes of environment strings alone. Because this is larger than the maximum UDP packet size, we seem to be out of luck. Fortunately for us, the preceding loop does no parsing of each environment string, so there is no reason for a malicious user to use properly formatted (key=value) strings. It is left to you to verify that placing approximately 16,919 space characters between the keyword environ and the trailing carriage return should result in an overwrite of the saved return address. Since an input line of that size easily fits in a UDP packet, all we need to do now is consider where to place our shellcode. The answer is to make it the last environment string, and the nice thing about this vulnerability is that we don’t even need to determine what value to overwrite the saved return address with, as the preceding code handles it for us. Understanding that point is also left to you as an exercise.
Above is the the exact extract from the book. I was trying to minimize it and I didn't have much time to post so i had minor spell check errors but the logic is the same. I just repost my early question so to make this post valid. I had:
he code handles this because when the function memchr returns a pointer, that pointer is pushed on the stack and the address pushed on the stack will be the starting point of our shellcode due the space before our shellcode?