I’ve published another security advisory about a remote code execution vulnerability with a CVSS score of 10,0 today. Affected are all available versions of the GetGo Download Manager, so if you’re still using this software you should immediately switch to a more secure one, because the GetGo project is dead, but still high-rated by cnet.com.
This article is a quick’n’dirty root-cause analysis of the bug, tested using a fully patched, DEP-enabled Windows 7x64:
When a website is requested for download, the application reads the HTTP Response Header values of the target page and copies the retrieved values to a temporary buffer, which is set to a fixed size of 4097 bytes, but this size is not used by the application to limit the input to the buffer. Instead it fills the buffer byte by byte until the terminating sequence “\r\n” is found. Therefore the application writes outside the expected memory boundaries if the HTTP Response Header is greater than 4097 bytes.
Let’s have a look at how the application handles HTTP downloads.
When you request to download a webpage like this, the debugger jumps in at the breakpoint at 0x004A4CF4, which is the entry point to the vulnerable code part:
The function call takes 3 arguments to further process them. A mapped c-style function call might look like this:
The stack contains the three arguments, whereat Arg1 points to a buffer at 0x0430C390, Arg2 is 0x00001001 and Arg3 is 0x00000078:
A small side-notice: You can completely ignore Arg3 in this vulnerability-case, because this value is only used as the timeout value for a ws2_32 select call which is involved in reading the bytes from the socket:
WTF happens here ?
The first important instruction set in vuln_func() is located at:
The function call at 0x004A3AA9 takes the same 3 arguments like the calling function, which are pushed on the stack:
It performs a simple memset call - so the function call looks like this:
Memset simply fills up memory space (arg1) with some unsigned char values (arg2) up to a specified number of bytes (arg3). This means that the buffer space at 0x430C390 is cleared out with 4097 bytes of 0x00s.
Next up: The vulnerable code part:
I think this really needs some clarification and therefore I divide this loop into two pieces.
Part #1 - Read one byte of the HTTP response header
The call at 0x004A3AFA takes three arguments:
whereas the first argument arg1 points to the buffer, arg2 is always 0x00000001 and arg3 is always 0x00000078. This function simply reads 1 byte from the received HTTP response header, puts the value into the buffer and returns 1 if the byte was successfully read.
A CMP instruction at the end indicates whether the byte was successfully read, otherwise the function will jump out of the loop at 0x004A3B0F.
Part #2 - Compare the received string to “\r\n”
The function call at 0x004A3B30 takes at the first look only two parameters - Arg1 contains a string-sequence which translates to “\r\n” and Arg2 is always 0x00000000.
Additionally the function itself reads the already received bytes from the stack to compare both values:
So the function call looks like this:
This function returns -1 if the string “\r\n” was not found, this leads to the following CMP:
The return value of the function search_teminator() is CMP’ed to -1, which means the JMP is taken and the application continues to execute the following instructions including a JMP back to the beginning of the code-part:
But if the string “\r\n” is found the function search_terminator() returns the position of the string, which leads to the CMP statement returning false” and the following statements are executed including a jump out of the loop:
The vulnerable code-part can be roughly translated to a C code snippet like:
Do you see the problem here ? The size argument (arg2) is used to prepare memory space using memset, but the loop reads all bytes from the header response until there’s a “\r\n” completely ignoring arg2. Now…if an attacker is able to supply more than 4097 bytes as the HTTP response header, the loop writes outside the expected memory boundaries, which leads to a stack-based buffer overflow condition, resulting in Remote Code Execution.
One PoC to pwn it
The following PoC creates an arbitrary webserver, that responds with an over-sized HTTP header:
…and this results in EIP control. Pretty cool :-)!