Decrypting SChannel TLS Traffic: Methods for Intercepting and Analyzing Windows Applications

Introduction to SChannel TLS

This article explores the process of hijacking terminal operations to intercept TLS keys for decryption, with a primary focus on TLS traffic from Windows applications utilizing the SChannel TLS component. This includes applications like IIS, RDP, Internet Explorer, older versions of Edge, Outlook, PowerShell, and more. Applications that use OpenSSL or NSS, such as browsers other than Internet Explorer and the older Edge, are not included.

SChannel TLS

SChannel, also known as Secure Channel, is a Windows subsystem used whenever Windows applications want to perform any TLS-related actions, such as establishing an encrypted session with a remote server or accepting TLS connections from clients.

From a system perspective, SChannel implements the Security Support Provider Interface (SSPI) and is one of the SSP packages offered by Microsoft. Other SSP packages include CredSSP, Negotiate, NTLM, Kerberos, and Digest.

Examples of SChannel usage:

HTTPS Connections

Initiated by IE, Edge, PowerShell’s Invoke-WebRequest Received by IIS web server

RDP Connections

Client’s mstsc.exe Terminal services on the server (termsrv.dll in svchost.exe)

LDAP server dynamic directory’s LDAPS connections

Some WinRM (PS remoting) connections when the server HTTPS listener is enabled. PS remoting also supports SSL authentication using TLS client certificates, which is implemented through SChannel when enabled.

Previously mentioned browsers like Firefox and Google Chrome use different libraries for TLS processing, namely NSS and OpenSSL, thus their traffic is out of this article’s scope. However, NSS and OpenSSL are both open-source, with documented methods for exporting secrets; for Firefox and Chrome, key export is built-in and can be activated using the SSLKEYLOGFILE environment variable.

Decrypting TLS 1.2 Traffic and Ephemeral Keys with SChannel TLS

This research is not based on exploiting protocol vulnerabilities or weaknesses but rather on having complete control over the application or operating system establishing or accepting a connection, allowing us to retrieve any used keys and secrets to obtain the necessary information for decrypting TLS traffic.

Section 2.2 of the internal workings of TLS1 provides an excellent summary, which will not be detailed extensively in this article. Here’s a quick overview of key concepts involved in TLS1.2 connections and encryption:

Ephemeral Keys

Whenever a TLS session is created, multiple keys associated with this connection are generated. Some keys are for encryption, others for message verification. Different keys exist for different directions (client to server and server to client). These keys are referred to as ephemeral keys to emphasize their short-term nature compared to long-term keys like server TLS certificate keys.

Perfect Forward Secrecy

All cipher suites can be categorized based on whether they support Perfect Forward Secrecy (PFS). With non-PFS cipher suites, any encrypted connection can be decrypted using captured traffic and the server’s TLS private key. Conversely, for PFS cipher suites, you need the relevant session’s ephemeral keys to decrypt it.

Master Key

The process of forming ephemeral keys involves several steps. In TLS1.2, it begins with the server and client collaborating to produce some key material known as the Pre-Master Secret, which is then expanded into the Master Secret. Subsequently, a set of keys and IVs for encryption and authentication—write keys and MAC keys—is generated. MAC keys are only used with non-AEAD ciphers.

TLS Session Tickets

Multiple independent TLS connections can belong to the same TLS session, meaning keys do not need recalculating every time. The previous method used session IDs—the server sends a session ID to the client, which then uses it for subsequent connections. The server must store keys associated with the session, requiring considerable memory on the server. Thus, TLS session tickets (RFC5077) were proposed, whereby the server sends an encrypted session state to the client, encrypted with a key known only to the server, and the client returns it in the next connection. This implies that although ephemeral keys are supposed to be destroyed post-connection, they might persist in the memory of both the server and client. For details on the security implications of TLS session tickets, refer to section 12.

SSL Keylog File

To decrypt a TLS traffic dump, a method to provide this information is necessary. For TLS 1.2, the standard method supported by OpenSSL and NSS is using an SSL keylog file, where each line consists of a constant label string, a value identifying the TLS session, and the secrets’ value. As a reference, you can find Wireshark’s keylog parsing routine in section 4.

Retrieve secrets used in each session Associate these keys with the session

Client Random and Session ID

TLS1.2 keylog file supports pre-master or master secrets for sessions. Sessions can be identified using client random (a random, unencrypted value sent by the client during the TLS handshake) or session ID (an unencrypted value sent by the server). An example of a TLS1.2 keylog file is as follows:

“`javascript COPY

CLIENT_RANDOM  

Decrypting TLS 1.3 Traffic with SChannel TLS

Many aspects mentioned above regarding TLS1.2 also apply to TLS1.3. However, there are several alterations in how secrets are generated.

For TLS 1.2, the key generation scheme is as follows:

“`javascript COPY

(1) Pre-Master Secret => (2) Master Secret => (3) A set of write keys and IVs and possibly MAC keys for client and server

In TLS1.2, the keylog file format requires you to provide the secrets for step (1) or step (2).

For TLS1.3, the scheme has evolved into the following version (refer to RFC8446 page 93):

“`javascript COPY

(1) Input Keying Material (IKM) => (2) A set of Secrets: Early, Handshake, Master etc => (3) A set of keys and IVs

TLS1.3 keylog files also require you to provide the secrets from step (2). Unlike TLS 1.2, each TLS session requires multiple lines, with each line providing a specific secret and binding it to a TLS session using client random. You need at least four secrets:

  • Client and server handshake secret
  • Client and server communication secret

An example keylog file:

“`javascript COPY

CLIENT_HANDSHAKE_TRAFFIC_SECRET   SERVER_HANDSHAKE_TRAFFIC_SECRET   CLIENT_TRAFFIC_SECRET_0   CLIENT_TRAFFIC_SECRET_0  

Key Isolation

The Windows Schannel API features the concept of Key Isolation (refer to section 5), which makes it more difficult to leak various confidential data by storing it in a centralized isolated location. Assume we have a process (e.g., terminal services client, mstsc) that wants to establish a TLS connection. However, the actual TLS handshake will be performed in another process (i.e., lsass.exe), and the secrets generated during the handshake (i.e., TLS1.2 pre-master and master keys) will never leave the memory of lsass.exe, nor will they ever touch mstsc.exe. All these operations are transparent to the application, which only uses functions from schannel.dll.

The schannel.dll on the application side uses ALPC to connect to the schannel.dll on the lsass side behind the scenes (refer to section 1, figures 2.6 and 2.7). ALPC calls are processed by a copy of schannel.dll loaded into lsass.exe, and then various key-related tasks are performed using a set of cryptographic APIs (CNG, mainly implemented in ncrypt.dll and bcrypt.dll).

This mode of operation is not unique to SChannel and applies to all security providers implementing SSPI. When you call InitializeSecurityContext, this call is processed by the SpInitLsaModeContextFn callback on the LSA side, and then the result is passed to SpInitUserModeContext on the application side. This way, credentials don’t need to be kept in memory, and Windows applications can use NTLM or Kerberos authentication.

For us, this means that lsass.exe is a good place to extract all ephemeral TLS keys used by any SChannel-enabled application. We need to hook the key creation/manipulation paths or find a way to reliably locate them in memory. We also need to bind them to a TLS session to utilize the obtained keys, preferably using methods supported by Wireshark (i.e., session ID or client random).

Objective

Our objective is to decrypt SChannel TLS traffic using Wireshark under full control of the application and/or operating system on the client or server side of the connection. Compared to the problem statement in Jacob Kambic’s paper1 (extracting keys from memory dumps), this approach is more flexible as it allows using memory scanning, debugging, and function hooking. Other key requirements are as follows:

Avoid relying on session resumption and other mechanisms, preventing keys from being cleared from memory when connections are closed; As much as possible, avoid relying on hardcoded offsets or other Windows and/or library-version specific elements; Extract keys from both ends of a connection; Extract keys from an area without requiring administrator privileges, i.e., not touching lsass.exe’s memory. Similar to the methods proposed in section 10.

Fetching TLS1.2 Keys

For TLS1.2, obtaining the pairing of client random and keys allows generating a keylog line that can be inserted into Wireshark for decryption.

Normal (Non-Resumption) Sessions

SslGenerateMasterKey

In a normal (non-resumption) TLS1.2 session, the SslGenerateMasterKey function in ncrypt.dll is called.

Refer to the official documentation of SslGenerateMasterKey:

“`javascript COPY

SECURITY_STATUS WINAPI SslGenerateMasterKey( _In_ NCRYPT_PROV_HANDLE hSslProvider, _In_ NCRYPT_KEY_HANDLE hPrivateKey, _In_ NCRYPT_KEY_HANDLE hPublicKey, _Out_ NCRYPT_KEY_HANDLE *phMasterKey, _In_ DWORD dwProtocol, _In_ DWORD dwCipherSuite, _In_ PNCryptBufferDesc pParameterList, _Out_ PBYTE pbOutput, _In_ DWORD cbOutput, _Out_ DWORD *pcbResult, _In_ DWORD dwFlags );

As indicated, the fourth parameter is annotated as Out (a type of “header annotation”), meaning that this pointer will be filled with the key address at the end of the function call.

Acquiring the Master Key

Following the address pointed by the *phMasterkey pointer leads to the NcryptSslKey structure.

SChannel TLS >

This structure contains an important magic value BDDD at offset 0x04 (see section 1, page 77), and at offset 0x10 (x64 case, 0x0C for x86), it contains a pointer to another magic value 5lss structure (see section 1, pages 64-68), viewed in the experimental environment memory (x64) as follows:

SChannel TLS >

Following the pNcryptSslKey pointer address 000002a8bcd81e70 entersSslMasterSecret` structure, hereafter referred to as SSL5 structure

According to section 1, page 68, the master key itself is located within the SSL5 structure at offset 0x1C (x64, 0x14 for x86), and is 48 (0x30) bytes in length:

Acquiring the Client Random

Returning to SslGenerateMasterKey, note the parameter list:

“`javascript COPY

_In_ PNCryptBufferDesc pParameterList ... pParameterList [in] A pointer to an array of NCryptBuffer buffers that contain information used as part of the key exchange operation. The precise set of buffers is dependent on the protocol and cipher suite that is used. At the minimum, the list will contain buffers that hold the client and server supplied random values.

The client and server random are precisely what we need to bind the keys to the session. The NCryptBuffer and NCryptBufferDesc structures are documented in the MS Reference Source for the .NET framework, consulted in section 17:

“`javascript COPY

typedef struct _NCryptBufferDesc { ULONG ulVersion; ULONG cBuffers; PNCryptBuffer pBuffers; } NCryptBufferDesc, *PNCryptBufferDesc; typedef struct _NCryptBuffer { ULONG cbBuffer; ULONG BufferType; PVOID pvBuffer; } NCryptBuffer, *PNCryptBuffer;

Following the pParamterList pointer to reach the NcrytBufferDesc, the number of buffers is obtained at offset 0x04, and the pointer to the array of NCryptBuffer structures is found at offset 0x08:

Following the pBuffers pointer:

At offset 0x04 in the pBuffers structure array is the data type of the buffer:

BufferType=0x14=20 denotes NCRYPTBUFFER_SSL_CLIENT_RANDOM BufferType=0x15=21 denotes NCRYPTBUFFER_SSL_SERVER_RANDOM

When BufferType is 0x14, the pointer to client random data is at offset 0x08