Once again I’m gonna talk about the 2nd version of a service from DEF CON 22’s CTF finals. This time it’s wdub; a simple HTTP server.
Version 1 had an integer overflow which lead to a stack overflow.
(edit: DEF CON CTF Challenges available @ http://shell-storm.org/repo/CTF/Defcon-22-finals/)
It turned out version 2 had two bugs. A use after free, and an integer overflow buf. To start with version 2 added a new http method – EVAL, where it ran something over the POST contents (with optional decompression), it also made POST requests to a file ending in ydg a script type page with <?ydg tags… that causes the same evaluating to happen.
I spent the first bit of time reversing the new command to figure out what it was doing as I didn’t have an SLA check network traffic yet. The first part was figuring out the language. It turned out to be a simple
var = command arg1 arg2 ...
OR
command arg1 arg2 ...
Depending on if the command allocated a new variable.
The commands:
[name]=gimme [size] - allocate item - first 4 bytes of data is size... Yo [name] - lookup item: switch field8 if 1,2,3 copy 0,1,3 btytes of databuf to output else copy *databuf (int) bytes to output putitin [item1] [item2] [item3 or num] - FIXED SIZE ONLY offset = item3.data or num memcpy(item1 + offset, item2, item2.size) pullitout [item1] [item2] [item3 or num] - FIXDED SIZE ONLY offset = item3.data or num memcpy(item1, item2+offset, item1.size) nomo [item] - free item [name]=dupme [old_name] - duplicate item [name]=shebewalkinfunny [number] - new 4 byte number [name]=datbetta [number] - new 2 byte number [name]=pobastard [num] - new 1 byte number [name]=makeit [num] - expand buffer bonut [name] [offset] - bit invert if not 1,2,4 byte value: bit flip byte at offset (+4) oudahere [name] [num1] [num2] - delete num2 bytes at num1 from name; no check on type of item (arb length or fixed length)** putdatader [name1] [name2] - append item2 to item1 (arb data only) slipitin [item1] [item2] [offset] - insert item2 into item 1 at offset (arb data only) slapitup [item1] [item2] [item3_or_offset] - copy item2 into item1 at offset (overwrite) (arb data only) [name]=moagin, eatdata, splitdat, screwdat [item2_or_num] [item3_or_num]; +, -, *, / name = num1 OP num2 if item1 doesn't exist, creates 4 byte item exite, bandit, borit [item1] [num] - for fixed size items xor, and, or item = item1 OP num exite, bandit, borit [item1] [offset] [num] - for var size items xor, and, or item[offset] OP= num (byte) deyseme ... bit shifts >>, <<, ror, rol
The use after free bug is due to the dupme command not doing a deep duplication. It only duplicates the data storage structure of:
char* name
char* data; //if a buffer it's a pascal style string buffer with the length being the first 4 bytes, otherwise it's a pointer to a 1, 2, or 4 byte buffer
int type; //0 - buffer, 1- char, 2 - short, 3 - dword
However the bug I went after was in the putitin command – something that would copy a 1,2,4 byte value into an offset location in a buffer. It turns out the check for if this size of the buffer is large enough was written like:
if (offset + 4 <= *(uint32_t*)(dest->data)) ...
Now if you can notice here.. if offset == -4, then offset+4 will be 0.. and 0 is < = any unsigned value. So good.. we can overflow the offset.. well what happens with that.. it turns out the copy to copy the data is:
memcpy((dest->data)+4+offset, src, 4)
And from the fact that the “size of buffer” is stored at the first 4 bytes of the data pointer (eg pascal strings) we can set the size to whatever we want.. which means if we set it to -1, we can then write ANYWHERE in memory.
Well that’s great.. ASLR is enabled.. well .. looking at the structures used, it turns out that the eval structure, has an field for length written (using the Yo command to write).. well we can change that length as we know the relative offset of that field to our allocated data (deterministic heap ftw)
Structure used to store eval results:
struct eval_result
char *outBuf;
uint32_t bytesout;
...
The outBuf is set by the function calling into the eval, and is a stack variable. it then uses the bytesOut to send the data at that location to the client. Well now we can leak addresses…
So we now know where the stack is. And since we can write anywhere, we can change the address of the data pointer in our buffer once we change the size… then we can change the stack..
So the final sequence is:
"b=gimme 4"
"c=shebewalkinfunny -1"
"putitin b c -4" #enable write anwhwere
"d=shebewalkinfunny 1024" #how much we want to leak
"putitin b d -424" #offset of data point to bytesOut