Wireshark-dev: Re: [Wireshark-dev] Support for TLS1.2 decryption using derived keys
From: Peter Wu <peter@xxxxxxxxxxxxx>
Date: Thu, 30 Apr 2020 11:58:35 +0200
Hi George,

On Thu, Apr 30, 2020 at 10:37:49AM +0300, webpentest wrote:
> Hello list,
> 
> I'm currently working on implementing a SSLKEYLOGFILE-like functions for
> TLS connections that use Windows SChannel APIs (e.g. IE/Edge, as well as
> other windows apps such as RDP client). SChannel does not expose its
> keys, though some research was done on recovering them (see [1] and [2]).
> 
> 
> I won't describe here the inner workings of schannel and key isolation
> in Windows, but the bottom line is as follows:
> 
> 1. The master key is only available to the lsass.exe process and
> accessing it thus requires elevated privileges. Furthermore, there might
> be additional restrictions that prevent event admins from accessing the
> memory of lsass.

This would be the ideal approach as access to the master secret provides
full functionality. Apart from the links shared before, I found these
references in my notes which might help with such an implementation:
https://reverseengineering.stackexchange.com/questions/211/decryping-tls-packets-between-windows-8-apps-and-azure
https://reverseengineering.stackexchange.com/questions/2681/is-it-possible-to-decrypt-an-ssl-connection-short-of-bruteforcing

Yes it will require some privileges, but someone with more expertise on
Windows might know their ways.

> 2. The derived keys, however, are available to the actual process that
> uses schannel. Notably, I can extract the client and server write keys
> and IV's, as well as the client random (see section 6.1 of RFC 5246,
> page 17).

In my notes I also found this reference describing extraction of the
"write keys", I suppose that is what you are using?
https://social.technet.microsoft.com/Forums/en-US/4041d78a-21bb-44fd-9a96-6579ea8129d1/obtaining-sslkeylogfilelike-data-from-edge-et-al-schannel-clients

> This means that it is possible to decrypt schannel traffic without the
> need for elevated privilege, by extracting and using those derived keys,
> but wireshark currently lacks the ability to ingest these values from a
> keylog file. Currently it can use derived keys for decryption only for
> TLS1.3 connections (see [3]).

Just to be clear about what "derived keys" are. You are referring to the
"write keys". The "derived keys" in that [3] link for TLS 1.3 are
comparable to the "master secret" in TLS 1.2. In TLS 1.2 and before the
master secret is sufficient to derive all those write keys that may be
used in the TLS session. In TLS 1.3, there are many more different
secrets for handshake encryption, application data encryption, and so
on: https://tools.ietf.org/html/rfc8446#page-93

The same write keys are present for TLS 1.3 though (see RFC 8446,
Section 7.3).

> I've thrown together a quick and dirty implementation of using derived
> keys to decrypt TLS1.2 (see [4]) and verified that it works: with the
> keys that I extracted I was able to decrypt successfully the RDP
> connection that I made from a windows client.
> 
> So, now my question is as follows:
> 
> 1. Considering the limited use-case for this functionality (using
> derived keys to decrypt TLS1.2), does it have a chance of being merged
> into upstream?

The idea could certainly be considered. There are limitations, but I
suppose that these would be preferable over having no decryption
capability at all. I think that this idea could be extended for TLS 1.3
as well.

Microsoft added an experimental implementation of TLS 1.3 in Windows 10,
version 1909:
https://docs.microsoft.com/en-us/windows/whats-new/whats-new-windows-10-version-1909

The following instructions to enable TLS 1.3 support was taken from
https://github.com/microsoft/msquic/blob/master/docs/BUILD.md#test-instructions

    reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.3\Server" /v Enabled /t REG_DWORD /d 1 /f
    reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.3\Server" /v DisabledByDefault /t REG_DWORD /d 0 /f
    reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.3\Client" /v Enabled /t REG_DWORD /d 1 /f
    reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.3\Client" /v DisabledByDefault /t REG_DWORD /d 0 /f

That document is part of Microsoft's QUIC stack which they opensourced
two days ago under the MIT license,
https://techcommunity.microsoft.com/t5/networking-blog/msquic-is-open-source/ba-p/1345441

QUIC relies on TLS 1.3 for keys an encryption (see
https://tools.ietf.org/html/draft-ietf-quic-tls), so it might be worth
checking out those sources to understand the current situation:
https://github.com/microsoft/msquic/blob/master/src/platform/tls_schannel.c

A particularly interesting starting point is the
QuicTlsWriteDataToSchannel function which calls AcceptSecurityContext
(for a server) or InitializeSecurityContextW (for clients). There is a
BufferType = SECBUFFER_TRAFFIC_SECRETS which contains "Traffic Secret"s.
The type of this buffer is "SEC_TRAFFIC_SECRETS" which seems to be
defined in some external header.

One search query later pointed me to this definition from
https://www.codemachine.com/downloads/win10.1903/ntifs.h

    //
    //  Traffic secret types:
    //
    typedef enum _SEC_TRAFFIC_SECRET_TYPE
    {
        SecTrafficSecret_None,
        SecTrafficSecret_Client,
        SecTrafficSecret_Server
    } SEC_TRAFFIC_SECRET_TYPE, *PSEC_TRAFFIC_SECRET_TYPE;

    #define SZ_ALG_MAX_SIZE 64

    typedef struct _SEC_TRAFFIC_SECRETS {
        wchar_t SymmetricAlgId[SZ_ALG_MAX_SIZE];     // Negotiated symmetric key algorithm. e.g. BCRYPT_AES_ALGORITHM.
        wchar_t ChainingMode[SZ_ALG_MAX_SIZE];       // Negotiated symmetric key algorithm chaining mode. e.g. BCRYPT_CHAIN_MODE_GCM or BCRYPT_CHAIN_MODE_CCM.
        wchar_t HashAlgId[SZ_ALG_MAX_SIZE];          // Negotiated hash algorithm. e.g. BCRYPT_SHA256_ALGORITHM or BCRYPT_SHA384_ALGORITHM.
        unsigned short KeySize;                      // Size in bytes of the symmetric key to derive from this traffic secret.
        unsigned short IvSize;                       // Size in bytes of the IV to derive from this traffic secret.
        unsigned short MsgSequenceStart;             // Offset of the first byte of the TLS message sequence to be protected with a key derived from TrafficSecret. Zero to indicate the first byte of the buffer.
        unsigned short MsgSequenceEnd;               // Offset of the last byte of the TLS message sequence to be protected with a key derived from TrafficSecret. Zero if the secret is for the encryption of application data or decryption of incoming records.
        SEC_TRAFFIC_SECRET_TYPE TrafficSecretType;   // Type of traffic secret from the TRAFFIC_SECRET_TYPE enumeration.
        unsigned short TrafficSecretSize;            // Size in bytes of the traffic secret.
        unsigned char  TrafficSecret[ANYSIZE_ARRAY]; // Traffic secret of type TrafficSecretType, TrafficSecretSize bytes long, used to derive write key and IV for message protection.
    } SEC_TRAFFIC_SECRETS, *PSEC_TRAFFIC_SECRETS;

The traffic secret is described in
https://tools.ietf.org/html/rfc8446#section-7.3, it is the same
handshake and application data secret that Wireshark already supports!
So for TLS 1.3 support, hopefully you can use this mechanism without
requiring further modifications to Wireshark. Maybe something similar is
available for TLS 1.2 and earlier support. If not, then we could add
support for write key/iv, but only as a last resort.

The TLS 1.2 master secret could enable verification of the "verify_data"
field. This has not been implemented yet, but it is a possibility.

> 2. If yes, is there a person familiar with wireshark's tls dissector,
> who is willing to advise me on what is the best way to implement these
> changes? I'm ready to spend time on reimplementing the patch, but I'll
> need someone for guidance and review.

I made some suggestions above on the high-level approach. If you have a
concrete patch I'd suggest submitting it for review per
https://www.wireshark.org/docs/wsdg_html_chunked/ChSrcContribute.html

Some quick comments:

- The dummy CLIENT_RANDOM is not needed, the state tracking has to be
  updated instead. Right now it expects either a premaster secret, a
  master secret, or an encrypted premaster secret + RSA private key.
  You'd have to extend this to support the fourth case.
- Do not allow the QUIC_ prefix in the regex. It is deprecated and will
  be removed later.
- The write key and write IV are usually available at the same time.
  What about defining a single format such as
  "WRITE_KEY <crand> <client key> <client iv> <server key> <server iv>"?
  This would reduce the number of hashtables required as well. For
  non-AEAD ciphers there is also a client/server MAC key for verifying
  the decryption result. In theory these could also be added to ensure
  full functionality. Not sure how important it is.
-- 
Kind regards,
Peter Wu
https://lekensteyn.nl

> Regards,
> George Noseevich.
> 
> 
> [1]
> https://www.blackhat.com/docs/us-16/materials/us-16-Kambic-Cunning-With-CNG-Soliciting-Secrets-From-SChannel.pdf
> 
> [2]
> https://osqa-ask.wireshark.org/questions/61698/decrypting-website-accessed-through-internet-explorer
> 
> [3] https://bugs.wireshark.org/bugzilla/show_bug.cgi?id=12779#c17
> 
> [4]
> https://github.com/ngo/wireshark/commit/1d86cc67fac2bff5a3ceb392e469b0d443cd9d22