Rolodex Walk-through (DefCon WarGamez CTF 2007)
The Rolodex service took commands from the network, including the
ability to read ":"-separated files and report back information from them.
more detailed disassembly info coming soon...
After comparing the various "read" and "memcpy" sizes to the stack and "calloc"
allocation sizes, we discovered that the "LOAD" command did some rather
inappropriate math when copying file contents into the newly allocated
buffer. Additionally, no path sanitization was performed, meaning one
could issue commands like "LOAD /etc/passwd" or "LOAD /tmp/evil.data".
While examining the caller's use of the resulting allocated buffer,
it was clear it was a 0x28c byte buffer, where the last two 4-byte chunks
were being used in a linked list. Without an overflow, these two pointers
("next" and "prev", as we'll see) were intended to be NULL.
After reconstructing the actions taken against the buffer, we see the following
actions during the normal course of running the "LOAD" command.
The program starts with a NULL global "ulist" pointer:
The file opened during the "LOAD" command is read one line at a time into
newly allocated and cleared memory regions.
The first line's allocated buffer is shown as
region "A", with its contents being filled from the first line of the file.
Note that the "next" and "prev" pointers are NULL due to the entire region
Now the region is inked into the global linked list. For the first node of the list, only
"ulist" it updated to point at it:
For the next line of the file, a new region ("B") is allocated (and cleared), and its
contents filled from disk:
Then it is linked into the global linked list. In this case, the first
node ("A") has its "next" pointer set to point at the second node ("B"),
and the second node ("B") has its "prev" pointer set to point back at node "A":
For the third line of the file, another region ("C") is allocated (and cleared), and its contents filled from disk:
Finally, we link it into the list, as with the other nodes, updating the new
node's "prev" pointer, and the prior node's "next" pointer:
Since the function handling the newly allocated memory regions gets its math
wrong, it is possible to write over the "next" and "prev" pointers if there
are too many characters between ":"s in the input file. What does this get
us? This means we can sneak a node into the linked list.
By overflowing the "next" pointer, we can force the list to suddenly have
an extra node after our first node is linked to "ulist". This means we
control where region "B" is located in memory:
As a result, when the next line is read in from the file (which will
populate the contents of region "C" in this diagram), node "B" will
have its "next" pointer updated but only if "next" was NULL
Results: we can change a value of 0x00000000 located anywhere in system
memory to a point to a newly allocated region of memory that contains bytes
under our control. How is this useful?
As it turns out, the libc "dtors" contains a NULL-terminated list of exit
handlers that get called during program shutdown. All we need to do load
region "C" with our shellcode (line 2 of the file), and aim 0x284 bytes above
the "dtors" in the overflowed contents of region "A" (line 1 of the file).
By SSHing into a target machine, we could generate a "corrupt" data file
with two lines: the first containing an overflowed "next" pointer to the dtors,
and the second containing the shellcode. Then we
connect to the Rolodex service, issue a "LOAD" for the file, and then "QUIT".
During shutdown, the dtors would run, executing our shellcode (which contained
a shell command to overwrite the key file with our team's overwrite token).
As a quick aside, it seems that while we abused the linking option,
sk3wlmaster used the UNlink operation, which didn't have the same
limitation of only ovewriting an already-NULL address.
example code coming soon...