Google

Exploiting MS14-066 / CVE-2014-6321 (aka “Winshock”)

Written on:November 29, 2014
Comments
Add One

Introduction

I think enough time has passed now to provide a little more detail on how to exploit MS14-066 schannel vulnerability (aka “Winshock”). In this post I won’t be providing a complete PoC exploit, but I will delve into the details on exactly how to trigger the heap overflow along with some example modifications to OpenSSL so you can replicate the issue yourself.

This vulnerability was announced while I was on vacation so I didn’t have a chance to analyze it right away. Lucky for me, the research team at BeyondTrust posted this concise write-up, keying in on the vulnerable code: http://blog.beyondtrust.com/triggering-ms14-066. When I first looked at the specifics of the vulnerability, I admittedly discounted it’s severity solely for the fact that it seemed necessary to have some form of mutual certificate-based authentication to trigger — still a big deal but not as widespread as I first imagined (or so I thought). Then, a couple of days later, Malware Tech posted this gem: http://www.malwaretech.com/2014/11/how-ms14-066-winshock-is-worse-than.html. Wow…triggering the exploit on web servers even if they were set to ignore certificates definitely renewed my interest. @MalwareTechBlog subsequently released this nice write-up with additional details, but by that time I was already deep into examining the exploit (though it’s still worth a read if you haven’t already): http://www.malwaretech.com/2014/11/ms14-066-in-depth-analysis.html.

If you’ve already had a chance to read these sources, please bear with me, because for the sake of completeness, I will be repeating some of the information.

First, let’s take a closer look at the vulnerable function…

schannel!DecodeSigAndReverse

Here is the disassembly for the pertinent portions of the DecodeSigAndReverse function (found in schannel.dll) which is responsible for decoding the encoded certificate signature.

ms14066_01-1

Of interest to us are the calls to CryptDecodeObject the the two subsequent calls to memcpy (found in the lower left yellow box), which is what ultimately triggers this vulnerability. I’ll get into more details but first it’s important to note that to trigger the vulnerable memcpy, we need to somehow force the server to verify a client-provided ECC certificate (as indicated by the cmp ebx, 2Fh instruction highlighted below).

ms14066_02

Most web servers will ignore a submitted client certificate (unless certificate authentication is required). However, as @MalwareTechBlog pointed out, vulnerable IIS servers will actually process a certificate that is forcefully presented regardless of the configured SSL settings. I’ll be demonstrating this vulnerability only for IIS on port 443, though you may be able to replicate similar results for RDP.

The Setup

The first thing you’ll need to do to trigger this exploit is configure IIS SSL settings (I used a Win 7 box) as follows:

ms14066_03

Generate a self-signed certificate (using OpenSSL), upload to your Windows box and bind it to your site:

ms14066_04

Now generate an EC cert/key pair to use on your “attacking” machine. Next you’ll need to download the OpenSSL source. For my test setup I used version 1.0.1j on a Kali box. Since you’re probably going to be modifying and recompiling many times, you may want to use the .configure script to permanently set any desired build variables (such as install location).

Once you’ve got your web server configured, your client cert/key generated, and openSSL source downloaded, it’s time to make some modifications, specifically to s3_clnt.c. First, we need to force the sending of our client certificate which can be done as follows:

ms14066_05

Notice I’ve altered the case statement logic to force a call to ssl3_send_client_certificate.

Next you’ll need to force certificate verification, which can be done with the following modification to make a call to ssl3_send_client_verify:

ms14066_06

Connect a remote kernel debugger to your test IIS box and set a breakpoint on schannel!DecodeSigAndReverse. Then connect via the modified OpenSSL s_client and you should trigger your breakpoint:

ms14066_39

ms14066_08

Once you’ve confirmed you can enter the vulnerable function, it’s time to delve into the details to understand how to exploit it.

A Closer Look

If you refer back to the disassembly of DecodeSigAndReverse, one of the the initial things to happen is the first of two calls to CryptDecodeObject.

ms14066_07This first call sets pvStructInfo to null to determine the size of the buffer needed to hold the decoded signature for proper memory allocation (which is handled by the subsequent call to SPExternalAlloc).

The second call to CryptDecodeObject is the one of interest.

ms14066_10

Testing this with an unmodified EC cert and key pair you should see something similar to the following for pcbStructInfo and pvStructInfo:

ms14066_09

You can see before the call that the designated size is 50h and the pvStructInfo structure is 0’d out and ready to receive the decoded signature. cbEncoded holds the size of the encoded signature, which in this case is 46h. The encoded signature looks as follows:

ms14066_12

* Note: in the above pic, ebp+0ch shows the size of the encoded signature (46h) and the memory at location 002dc57b holds the encoded signature.

When the second CryptDecodeObject function returns, pvStructInfo now holds the decoded signature:

ms14066_13

Now we can shift our attention to the two memcpy functions that follow.

ms14066_14

The first memcpy will copy the number of bytes of the decoded signature designated in [esi] (in this case 20h or 32d) to a designated buffer (Dst). In the screenshot below, you can see that the contents of this buffer after the call to memcpy contain the first 32 bytes of the decoded signature returned by CryptDecodeObject.

ms14066_15

Similarly, the second memcpy copies the remaining 32 bytes of the decoded signature to the adjacent reserved space in memory (Dst).

ms14066_16

It’s the second memcpy function that is vulnerable to a heap overflow and will be the focus of our attention. To understand why it’s vulnerable to an overflow condition, we need to see how the destination buffer (Dst) is allocated which actually happens in the CheckClientVerifyMessage().

ms14066_17

Notice in the third block below (before the call to DecodeSigAndReverse) is the call to SPExternalAlloc, which reserves the memory (Dst) where our decoded signature will be copied via the two calls to memcpy shown previously.

ms14066_19

You’ll recall each of those calls to memcpy copied 32 bytes (20h) of the decoded signature to adjacent portions of memory to fill a total of 40h bytes. This 40h bytes is reserved by SPExternalAlloc by taking the total key size (in this case 256 bits), dividing it by 8 (to convert to bytes) and doubling it (256 / 8 = 32 * 2 = 64d or 40 h). The dangerous assumption here is that the decoded signature will always be twice the size of the key. We’re about to see why that’s not true.

Modifying The Encoded Signature

The first thing we want to do is have some control over the size of the encoded signature represented by cbEncoded. If you refer to the call to CheckClientVerifyMessage(), you’ll notice that cbEncoded is passed as a parameter, which means we need to back up even further to the function DigestCertVerify().

ms14066_21

We can see at location 0xXXXX98CE of DigestCertVerify that cbEncoded is located at [esi+1]. It turns out [esi] merely points to the start of the encoded signature structure which looks like this:

ms14066_22

Notice [esi+1] holds the value of cbEncoded (47h). The bytes that follow make up the encoded signature that will eventually be passed to CrypteDecodeObject() within DecodeSigAndReverse(). Here is a look at that call so you can see they are identical:

ms14066_23

The structure that holds our encoded signature is defined on MSDN as follows:

ms14066_26

For the purpose of this demo, I’ll call this structure sig. We’ve identified the total size of sig by the byte located at sig[1]. Structure members r and s are decoded and copied via the two memcpy operations. What’s even more interesting is that two other bytes of sig (which is under our control) dictate the size of r and s used in each memcpy operation.

ms14066_24

This means we have control over how many bytes are written via each memcpy and can use that to overwrite the reserved heap buffer. In addition, other than a few required byte values, the majority of the encoded signature is irrelevant, meaning we can inject any values we wish.

In addition, submitting an invalid encoded signature to CryptDecodeObject doesn’t result in an error. Instead, it simply returns the invalid encoded signature that we passed it and that data is subsequently written to the heap via the vulnerable memcpy functions. This means we can predict the subsequent return values that will be passed to the vulnerable memcpy functions and written via the heap overflow.

From my brief testing, I identified the following required values for sig:

  • sig[1] = cbEncoded (total size of sig); my testing indicates that max size is \x81
  • sig[2] = \x30; required (leave unchanged)
  • sig[3] = (sig[1] -1); max size is \x7f
  • sig[4] = \x02; appears to represent data type that follows
  • sig[5] = size of r or memcpy 1; minimum value is 1
  • sig[6] = any single byte value such as \x00
  • sig[7] = \x02; appears to represent data type that follows
  • sig[8] = sig[1] – 7 = \x7a
  • sig[9]…sig[x] = any arbitrary values for heap oveflow

Triggering the Exploit

So, how can we influence the encoded signature value? Rather than changing the content of the certificate, we can modify the encoded signature directly in OpenSSL via the ssl3_send_client_verify function.

ms14066_38

A couple things to note about the above modified signature. The first memcpy is represented by the three bytes at psig[2]psig[4] (\x02\x01\x00). Again, I believe the first byte to represent the data type, the second byte is the size for memcpy, and the third byte is the content (src) to write. A single byte of \x00 will result in the first memcpy writing 32 null bytes to the first half of the reserved 64 bytes.

The next 32 bytes as well as up to 90 additional bytes will be consumed by what follows psig[7] and psig[8]. This is what results in the heap overflow.

For purposes of illustration, notice I changed the total size of the signature to a much larger value of 15,500. Although CryptDecodeObject limits the size of the total signature it will process (again, my testing indicated the limit is \x81), DecodeSigAndReverse accepts much larger values for the encoded signature (pbEncoded) which means arbitrary data can be written to memory before it’s processed by CryptDecodeObject Note: it appears that DecodeSigAndReverse properly allocates memory for the encoded signature and none of my tests resulted in an overflow condition as a result submitting a large signature; however, being able to write 10,000+ bytes of arbitrary data may come in handy.

You can see an example of this below:

ms14066_25

Following this through to the memcpy functions, the resulting heap overflow looks as follows:

ms14066_36

Not surprisingly, this overflow results in an Access Violation (yours may vary).

ms14066_37

ms14066_40

One thing to note about this basic demonstration is that the heap overflow may not be triggered immediately nor will the access violation caused by this simple demo be consistent. I found you can typically trigger it at-will by establishing another (valid) connection via an unmodified version of OpenSSL s_client. Here’s a quick 30 second video demonstrating what it looks like on the target machine.

As I said earlier, I won’t be providing a full working exploit PoC at this time nor will I be uploading my modified version of OpenSSL. That said, the information in this post should be more than enough to understand how this vulnerability can be exploited (though reliability will be a factor) and you may think of additional methods of exploitation. Regardless, this simple demo is still enough to interrupt IIS services and reboot the target machine so please do not test it against any servers unless you have explicit permission to do so!

Conclusion

To recap, the second memcpy found in schannel!DecodeSigAndReverse is vulnerable to a heap overflow condition due to the presumption of the decoded signature size (twice the size of the key) made in the calling function CheckClientVerifyMessage. Since we can control (to a degree) the size and content of the memcpy function (via the encoded signature) and CryptDecodeObject leaves the encoded signature input untouched, we can predict the values written in the heap overflow. This can all be triggered via some basic modification to OpenSSL.

Hopefully this gave you some additional insight into how this vulnerability can be triggered and exploited, and as always, apply those patches if you haven’t already!

Until next time,

Mike

53 Comments add one

  1. Daniel says:

    Great write up, although parts are beyond me. How feasible is a remote code exploit from this point?

    • Mike Czumak says:

      Thanks Daniel. Remote code exploitation is certainly feasible, though reliability is a factor. Though I haven’t seen it (so I don’t know what exactly it does), Immunity has released a PoC for its Canvas customers.

  2. Toby says:

    Hi Mike,

    Thanks for brilliant technical description!

    Quick question! how do i go about generate EC cert/key pair also where would you be using them?

    Regards

    much appreciate your help

    • Mike Czumak says:

      Thanks Toby. Regarding how to generate the EC cert, I would use OpenSSL (Google: generate ecdsa certificate openssl). You will use them with s_client when connecting to the server (see my screenshot).

  3. Alan says:

    I mucked about a bit with this last night. The setChar lines are presumably deliberately broken so I attempted to fix those to get openssl compiled, however wasnt able to get anything but schannel errors in my eventvwr.

    My C skills stop at me realising that psig is an array mind you.

    • Mike Czumak says:

      setChar is just a simple function I added to modify a single character.

      void setChar(unsigned char* oldValue, const char newValue) {
      *oldValue = newValue;
      }

      • Alan says:

        Wasnt able to trigger the issue against a Windows 7 Pro SP1 x64 test VM with no patches installed vs RDP or IIS.

        After including your function declaration I did make one other small alteration to the code which was to declare the array before we tried to use it.

        I find it oddly comforting that my limited ‘pigeon C’ and extensive linux knowledge (day job is *nix sys admin) was insufficient to trigger the vulnerability even after being provided all the ingredients on a plate here :-)

        • Mike Czumak says:

          It took some trial and error on my part to get everything working correctly. I was able to successfully replicate on both Win 7 32- and 64-bit machines. I did leave some “ingredients” out of my tutorial though I’ve filled in some blanks in my comments. Also note that you’ll have to ensure you’re not getting an “unable to verify” return code (21) from your server cert or the authentication process will never make it to cert verification (at least w/ the minimal openssl mods I made).

  4. phay says:

    in file s3_clnt.c,what is the relationship between “psig” and “p”?
    in my test,I set “p = psig”,but when windbg break into:
    schannel!CSsl3TlsServerContext::DigestCertVerify+0x1a8:
    001b:74df98ce 0fb606 movzx eax,byte ptr [esi]

    kd> d [esi] l 100
    000a02bf 00 46 81 30 7f 02 01 00-02 7a cc cc cc cc cc cc .F0.0…..z…..
    000a02cf cc cc cc cc cc cc cc cc-cc cc cc cc cc cc cc cc …………….
    000a02df cc cc cc cc cc cc cc cc-cc cc cc cc cc cc cc cc …………….
    000a02ef cc cc cc cc cc cc cc cc-cc cc cc cc cc cc cc cc …………….
    000a02ff cc cc cc cc cc cc cc cc-cc cc cc cc cc cc cc cc …………….

    The “81” is one byte behind cbEncoded byte,why?

    • Mike Czumak says:

      I’m not in front of my test machine now, but psig[1] should be where \x46 is located. psig is really the same as p, I just renamed it during testing as I was modifying various portions of the code. It may depend on where you’re declaring/defining it. At the beginning of the ssl3_send_client_verify function you should see something like:

      d=(unsigned char *)s->init_buf->data;
      p= &(d[4]);

      I simply added:

      psig = &(d[4]);

  5. phay says:

    Thank you for your tips!I have successfully triggered the crash.
    Can I have a copy of the Remote code exploitation POC, pls?
    If yes,pls send to my email:[redacted]
    Thanks!

    • Mike Czumak says:

      Glad it worked for you. One of the reasons I haven’t shared a more complete PoC is because I haven’t gotten reliable code execution to this point. If it’s not something that’s repeatable, it’s no better than a DoS. I haven’t had a chance to work on it over the last few days but I hope to take another look soon. And if I don’t get a chance anytime soon, hopefully the info I’ve provided is enough for someone else to get it reliably working! Btw, I hope you don’t mind but I redacted your email address from your comment just in case you didn’t want it publicly disclosed.

  6. uvbs says:

    great, nice work.
    i follow your step, but there was a problem i couldn’t handle it.

    Here is output:
    —————————————————————
    Loading ‘screen’ into random state – done
    CONNECTED(00000764)
    depth=0 C = CN, ST = XX, L = asdf, O = asdf, OU = asdf, CN = uvbsxx, emailAddres
    s = tjjtds@183.com
    verify error:num=18:self signed certificate
    verify return:1
    depth=0 C = CN, ST = XX, L = asdf, O = asdf, OU = asdf, CN = uvbsxx, emailAddres
    s = tjjtds@183.com
    verify return:1
    [+]Force Certicate
    [+]force cert verify
    3604:error:14099044:SSL routines:SSL3_SEND_CLIENT_VERIFY:internal error:.\ssl\s3
    _clnt.c:3136:
    ——————————————————————–
    may be my generated certificate is wrong! BTW, my compile system is xp, target system is win2012 , iis 8, TLS = 3.3

    • Mike Czumak says:

      I haven’t tested on xp but have you checked to see what resides at line 3136 of s3_clnt.c? That’s what’s triggering your error.

  7. Mike Alexander says:

    Dear Mike i tried to do this on my windows 7 x86 system hosted by virtual machine but couldn’t get it to crash. Can you please email me the modified openssl or POC ? would be a great help thx. My email: [redacted]

    • Mike Czumak says:

      The mod’d openssl file isn’t going to provide you much more insight than what I’ve included in the post and comments. I may be able to give you some pointers if you can provide some additional info. Are you receiving errors? Are you able to successfully exchange a cert? Were you able to hit the breakpoint of the vulnerable function on your target machine?

  8. F0rb1dd3n says:

    How can I fix it??

    ../libssl.a(s3_clnt.o): In function ssl3_send_client_verify':
    s3_clnt.c:(.text+0x3fe0): undefined reference to
    setChar’
    s3_clnt.c:(.text+0x3ff1): undefined reference to setChar'
    s3_clnt.c:(.text+0x4002): undefined reference to
    setChar’
    s3_clnt.c:(.text+0x4013): undefined reference to setChar'
    s3_clnt.c:(.text+0x4024): undefined reference to
    setChar’
    ../libssl.a(s3_clnt.o):s3_clnt.c:(.text+0x4032): more undefined references to setChar' follow
    collect2: error: ld returned 1 exit status
    make[2]: ** [link_app.] Erro 1
    make[2]: Saindo do diretório
    /root/Downloads/openssl-1.0.1j/apps’
    make[1]: ** [openssl] Erro 2
    make[1]: Saindo do diretório `/root/Downloads/openssl-1.0.1j/apps’
    make: ** [build_apps] Erro 1

    You said you will make an upload of modified OpenSSL, how can I download it?

    Thanks

    • Mike Czumak says:

      I was not planning on posting my mod’d version of OpenSSL but I gave all of the info that should be necessary to replicate. You’re getting this error because you haven’t defined the setChar function. I included what that function would look like in a previous comment.

  9. F0rb1dd3n says:

    Sorry, for my fault.

    and Thanks very much for your help.

    greetings

  10. Rich says:

    Mike,

    I’m getting a connection reset by peer error right after “[+] Forcing cert verification” that I suspect is caused by the immediately preeceding error 20 (unable to get local issuer certificate) and error 21 (unable to verify the first certificate).

    If you agree, is there any chance you’ll reconsider providing the ancillary cert generation incantation? :)

  11. Rich says:

    Mike, I worked past the cert issue, but was still unable to trigger the break point in spite of setting up my debugger like yours (http://www.securitysift.com/wp-content/uploads/2014/11/ms14066_08.jpg). I’d much appreciate any guidance you can give me.

    • Mike Czumak says:

      I’m assuming you’re now getting a return code of 18 (self-signed cert and no other errors). If that’s the case make sure that after you’ve set your breakpoint on your target you switch to the appropriate process context (.process /i [process]) — otherwise, the breakpoint will not hit. It probably goes without saying, but also make sure that the breakpoint is enabled (bl). Also, I’m not sure what EC key type you used, but I had no issues with prime256v1 so you might want to try that if you’re using something different.

  12. Gary says:

    I’m running in to similar problems in getting the debugger to trigger the breakpoint. I’ve set it up to break in the same way, and have similarly modified and compiled openssl. Am running IIS on a fresh install of Windows 7 x64.

  13. Rich says:

    Mike, I appreciate your continued patience with me (I say optimistically :)). Tracing reveals that, in my case, schannel!_imp_CertGetPublicKeyLength is returning 0. According to MSDN, this means the function is unable to determine the key length. I have to conclude I am doing something wrong in setting up my cert.

    Would you kindly take a look at my command line inputs and let me know if anything jumps out at you as wrong?

    root@kali:~/Desktop#openssl ecparam -name prime256v1 -genkey -param_enc explicit -out private-key.pem

    root@kali:~/Desktop#openssl req -new -key private-key.pem -x509 -nodes -days 365 -out server.pem

    root@kali:~/Desktop#openssl s_client -connect 192.168.211.199:443 -state -verify 5 -tls1 -cert ./client.pem -key ./private-key.pem

  14. Ako says:

    level of threat does this CVE pose to a windows network/domain?
    How might this vulnerability be exploited and how might an exploit work?
    Remedial action necessary to mitigate this CVE?

    thank you

    • Mike Czumak says:

      It’s a heap overflow that could allow for remote code execution, which is why Microsoft rated it Critical. Even in the most basic example that I posted here, it could lead to a denial of service condition. In terms of remediation, you should patch this one ASAP, especially for any public-facing servers (MS did have some issues with the initial patch for Server 2008/R2, Server 2012, Vista but those were addressed on Nov 18/Dec 9). There are other supplementary controls (such as network-based IPS) that may potentially identify and block exploit attempts (though I haven’t tested the effectiveness of any myself).

  15. Ako says:

    Ok thank you

    has MS released any patches yet?

  16. Ako says:

    is there any ways to make it clear the threat does this CVE pose to a windows network/domain

  17. mj says:

    good job,
    May i know which version of windows/schannel and openssl you used please?
    Is your IDA Pro loaded function name DecodeSigAndReverse automatically from symbols ?

    Thanks

    • Mike Czumak says:

      Thanks mj. The schannel.dll on my target win7 32-bit is dated 3/4/2014. Openssl version is listed in the post. Yes, IDA loaded the symbols/PDB.

  18. DrKN says:

    Well, I have been doing the test all days in last two weeks and realised that the verify return code (code 21) is not failing the exploit.
    Here is a note for people who want to try:
    test and understand every line you added/deleted! Asking for the working source do not gain yourself any knowledge :)

  19. alex says:

    Hi,
    would you mine pleaes countinue the tutorial of exploiting ?
    you had an excellent tutorial about the exploiting but it was not compelete.

    i mean the heap overflow, bypas ASLR DEP and etc

    thanks

    • Mike Czumak says:

      Alex,

      I definitely plan to continue the series, though I’ve been sidetracked with several other projects. I’ll get back to it as soon as I can. Thanks for the feedback!

      – Mike

  20. Zia says:

    Mike I have modified the code as per your this tutorial but I was unable to understand setChar function is doing? Can you please provide little elaboration on this

    • Mike Czumak says:

      Zia,

      Take a look at my comment from Dec 1 where I provide the code — it’s a simple function that just modifies the value of a character. Hopefully this answers your question.

      -Mike

  21. Ty says:

    Hey Mike,

    Great article. Thanks.

    Should this work against RDP also, or do tweaks need making for different services? I would have thought the exploit would be relatively generic since the SSL is triggered before the underlying protocol, but then again the different services may have different SSL configurations.

    Also, I modified the OpenSSL source so that it trusts self signed certificates and get “Verify return code: 0 (ok)”. I assume that should still work right?

    Thanks,
    Ty

    • Mike Czumak says:

      Re: RDP, if I recall from my testing, my mods will not work but I could be mistaken. Off the top of my head, I see no reason why your self-sign mod won’t work.

  22. 0xFFFF says:

    Hello Mike!

    Could you please share your POC code?
    And have you got any reliable exploit?

    Thank you!

    • Mike Czumak says:

      Everything needed to reproduce my results is available in the post (with additional info in the comments). No, I did not create a reliable exploit and unfortunately I haven’t had an opportunity to spend much time on it.

  23. M says:

    How did you decide which process to start debugging via kd? I have looked in all of my processes on my test machine and none of them have schannel.dll mapped in, so I can’t set a breakpoint on the buggy function.

    • Mike Czumak says:

      Change context to the LSASS process first. Then you can set your breakpoint on the schannel functions.

Leave a Comment

Your email address will not be published. Required fields are marked *