Monday, 30 March 2026

Blog 2 - Windows Vulnerability Research – Digging deeper into Secure Channel

 Edit: Oops, I was tired yesterday and initially called the bug at the bottom of this blog an integer truncation bug, but to be technically correct it is an unsigned integer overflow bug. Was tired and making some weird abstractions in my head.

Disclaimer: This blog series won't be very technical. It is intended to be easy to understand and follow. Like most of my blogs, I have no reason to be peacocking with technical jargon like all the infosec losers.

Overview and donation information: https://weirdquadratic.blogspot.com/p/blog-overview.html

To the reader: Feel free to share anything I write with those who need it. And be sure to make local copies as I am sure it is a matter of time until this blog will be taken down as well.

A high level strategy for finding software vulnerabilities

In the previous blog post (Blog 1) we set up debugging and triggered a top-level function in Secure Channel.

Generating a trigger PoC for a top-level function (an initial entry point for an attacker) in a software component is nearly always my first step. This can be the most frustrating part, especially if you are trying to craft a trigger PoC for something very complicated or poorly documented. Once you have a trigger PoC however, the next step is to begin exploring!

The code exploration step is usually the most time consuming but at the same time, the most relaxing once you possess a baseline of code reading and/or reverse engineering skills.

Software issues usually occur in areas of high complexity. This can be a lot of different threads interacting on the same objects. Or very complicated parsing of user input. Another area of interest is the interaction between different components. For example in the case of IIS, where Secure Channel returns to http.sys. Because http.sys and Secure Channel are likely written by different developers, so they may not have made correct assumptions about the format of data being returned and visa versa.

Digging deeper into Secure Channel

There is a feature in Secure Channel called “Mutual Authentication”. What this means is that both the client and the server must authenticate themselves. This is useful to restrict access to certain resources while also leveraging existing PKI-infrastructure.

The easiest way to enable Mutual Authentication is with the “Internet Information Services (IIS) Manager” GUI.

Forcing a certificate request

In the “Internet Information Services (IIS) Manager” GUI's left-hand pane go to <computer name> → sites → default Web Site. First Select “Ssl Settings” from the middle pane and make sure “Require Ssl” is enabled and “Client Certificates” is set to “require”.

Enabling Activate Directory Certificate Services

Next rather than using a self-signed certificate, let us use a proper domain certificate, while not needed for this tutorial, it will be useful in the future.

First we should enable “Active Directory Certificate Services”.

To do this go to the “Server Manager” and in the middle pane selected “Add roles and features”.

Then select as Installation Type “Role-based or feature-based installation” and under Server Roles select “Activate Directory Certificate Services”. Then go ahead and install.

Once that is done, a button should appear to configure the Certificate Services

Under Role Services select “Certification Authority”.
Under Setup Type select “Enterprise CA”.
Under CA Type select “Root CA”.
Under Private Key select “Create a new private key”.

Then just use the defaults for the rest and finish configuring.

Generating a domain certificate

From the “Internet Information Services (IIS) Manager” window go to (if it was already open before installing the certificate services, you may need to re-open it) <computer name> on the left-hand pane and in the middle pane double click “server certificates”.

Next on the right-hand pane click “Create Domain Certificate”

Make sure to set the common name (CN) to your domain (I.e bear.com) and on the next screen as Online Certification Authority, you should be able to select the one we just installed. Then finish.

Next on the left-hand pane go to <computer name>-> sites → Default Web Site

Select “Bindings” and click edit on the “https” binding we created in the previous blog and change the self-signed certificate to the domain certificate we just created. In addition make sure we enable “Negotiate Client certificate” and for simplicity also disable the use of TLS 1.3 for now, since TLS 1.3 is a little bit more complicated and harder to debug.

Next in the windows search bar, search for “Manage computer Certificates” and just go to personal → certificates and double click on the certificate we just created. Then go to the details tab and find “thumbprint” and copy this.

Open a terminal and run the following curl command:

curl --cert "LocalMachine\\MY\\02d8ff631752419efd659a4bf5f0cf0c91c62700" -v https://bear.com:443

Replacing the thumbprint part in --cert with the one you just copied and the site url with your domain name.

If all goes well it should throw an error about an invalid client certificate:

<h3>HTTP Error 403.16 - Forbidden</h3>

<h4>Your client certificate is either not trusted or is invalid.</h4>

(and so on)

This is not important for now.

Next open Wireshark and record on the loopback interface (or ethernet interface if running curl from a domain joined machine) and rerun the curl command.

You will see a "certificate request" handshake message coming from the IIS server. This only happens when mutual authentication is enabled and basically indicates to the client it is expecting the client to provide a certificate. Next in Wireshark you will then see the client providing a certificate (albeit a wrong one).

Let us trace this handshake with a debugger.

But first, we must download binary ninja and have a quick look at the decompilation so that we know where to put breakpoints.

Download Binary Ninja from here: https://binary.ninja/free/

Once Binary Ninja is installed, on your Windows Server Virtual Machine go to c:\windows\system32 and copy past schannel.dll to your host machine.

Launch Binary Ninja and Open schannel.dll.

Find the following function: Cssl3TlsServerContext::ProcessHandshake in Binary Ninja

This function is responsible for routing all the handshake messages to the correct function.

If we scroll down in this function we will see a call to Cssl3TlsContext::DigestRemoteCertificate.

This function is responsible for parsing incoming certificates. Be it from the server or client context.

Further down you will see another function called DoCertificateMapping.

This function will do the actual client authentication.

Launch a debug server on the Windows Server Virtual Machine using the following command (see previous blog for detailed instructions):

dbgsrvX64.exe -t tcp:port=1234

To make things a little easier, we can also connect to a debug server with binary ninja.
In “ debugger” select “Connect to debug server” and enter the port and ip.
Then press accept. Then go again to “debugger” at the top and this time select “Attach to process”. Then just press accept again and a small windows listing all the process running on your Windows Server VM should appear, find and select lsass.

Type bp schannel!DoCertificateMapping into the debugger window to place a breakpoint.

Let the debugger run again and rerun the curl command. If everything is setup correctly, this should trigger the breakpoint.

Now we can step through pseudo code using F7 and F8, which makes things a little easier. However, I do have a bug where the function names are not showing up (I'm using binja for the first time myself, I'm normally an IDA user, but it is too expensive), so I have to open up a second copy to see the function names in the pseudo code. If anyone knows how to properly load function names while debugging, please let me know.

With the debugger broken into at DoCertificateMapping there is a very easy trick to quickly make sense of all the code it ends up calling from this function onward. We can create a function trace.

https://learn.microsoft.com/en-us/windows-hardware/drivers/debuggercmds/wt--trace-and-watch-data-

This is elite level windbg magic, so please do not share with the plebs.

wt -i ntdll -i KERNELBASE -i CRYPT32

(this will ignore ntdll, KERNELBASE and CRYPT32 from the trace, as they can take up quite a bit of time otherwise)

The following trace will be created:

If we actually scroll through DoCertificateMapping in Binary Ninja, we will see a call to SslMapCredential. This does not appear in our trace.
So somewhere in one of the certificate chain building functions, we are failing. Lets see if we can get SslMapCredential to hit by configuring client certificate authentication.

Enabling IIS Client Certificate Mapping Authentication

In the server manager, we can install “IIS Client Certificate Mapping Authentication”. There is also an option to use “Client Certificate Mapping Authentication” and utilize Active Directory but we will explore that later as it is a little bit more involved to setup.

In the Internet Information Services (IIS) Manager UI go to <computer name> → sites → default web site and open the configuration editor. Navigate to system.webServer/security/authentication/iisClientCertificateMappingAuthentication

and change “enabled” to “True”.

Rerunning the curl command again and SslMapCredential should hit!

Creating a certificate to use for client authentication

Let us also create a valid client certificate we can use. Just the see what the normal execution flow is supposed to be.

Ideally we should use Active Directory Certificate Services to create a certificate for client authentication, but setting all of this up correctly can be a pain and we will focus on this in a later blog post.

We can use the instructions here to quickly generate a client certifcate: https://learn.microsoft.com/en-us/previous-versions/msp-n-p/ff650751(v=pandp.10)?redirectedfrom=MSDN

If you run the command at the bottom of that tutorial, it will install a client certificate in your user certificate store. You can then export it as a .pfx file and install it on your Windows Server virtual machine into the Current User -> Personal certificate store. Once you do that, grab the thumbprint and rerun curl.

curl --cert "CurrentUser\\MY\\9ef0e3693d025806aed6028567e7f08446c0e737" -v https://bear.com:443

When the breakpoint hits on SslMapCredential, we create a new trace using:

wt -i ntdll -i KERNELBASE -i CRYPT32.


We now see that we end up calling into LsaLogonUser. Which is the main api for authentication.

https://learn.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-lsalogonuser

For the purposes of this tutorial, we have burrowed deep enough into the code now. Lets see if we can find a bug.

The case of the unsigned integer overflow bug

Prior to DoCertificateMapping we call into Cssl3TlsServerContext::DigestCertVerify. Which basically verifies the digest of a certificate. So to construct a PoC (rather then using curl) to get past this is a little more elaborate then simply sending raw bytes over a socket.

Luckily there are TLS clients we can repurpose.

Installing Scapy

Scapy comes with a TLS client which is easy to modify. So let us go ahead and install Scapy.

In the previous blog we installed python on the Windows Enterprise (client) VM. So let us run our PoC from there. Install scapy on the Windows Enterprise VM by following the instructions here: https://scapy.readthedocs.io/en/latest/installation.html#windows

Note: If internet is not working, just remove the dns setting (in adapter settings, see blog 0) so we arn't pointing to our own dns server anymore.

In addition also make sure the cryptography module is installed by running:

pip install cryptography

Next download our proof of concept: https://github.com/BigPolarBear1/Blog/blob/main/Blog%202/tls_integer_truncation.py

edit: Actually this is an usigned integer overflow bug. Not truncation. Was just mentally tired yesterday and making some weird abstractions in my head.

This is simply a modified version of the scapy TLS client: scapy/scapy/layers/tls/automaton_cli.py at master · secdev/scapy

We will need the client certificate and private key which we generated earlier. If you go to the certificate user store where it was installed, you can extract it as .pfx and use OpenSsl to convert that to a seperate key and certificate file which can be used with the Scapy script, using the following commands:

For the private key:

openssl pkcs12 -in clientcert.pfx -out private.key -nocerts -nodes -passin pass:YourPassword

For the certificate:

openssl pkcs12 -in clientcert.pfx -out certificate.pem -nokeys -passin pass:YourPassword

The proof of concept will trigger an unsigned integer overflow bug. This works by providing a supplemental data message: https://www.rfc-editor.org/rfc/rfc4680.html

At the very bottom of the PoC modify the following line (line 1502):

t = TLSClientAutomaton(server="172.20.136.114",dport=443,version="tls12",server_name="bear.com",mycert="certificate.pem",mykey="private.key")

Make sure to change server to the IP address if your Windows Server VM and the server_name to that of your domain. Also provide the client certificate and key.

The first modification I have made in the scapy TLS code is adding a new extension for the TLS ClientHello handshake message.

If we run the proof of concept and record with wireshark we will see these same bytes in our ClientHello:

This is the TLS User Mapping Extension, as defined by the following RFC: ietf.org/rfc/rfc4681
This extension will allow us to send a Supplemental Data handshake Message and provide custom User Mapping Data. Which is normally included in the client certificate, but can also be defined with the Supplemental Data handshake Message instead.

I have modified the scapy code and added the following lines to send a TLSSupplementalData message:

What this does is send a TLS Supplemental Data handshake message after receiving the certificate request by the server. Which we can observe in the above WireShark capture.

The CSsl3TlsServerContext::DigestSupplementalDataMsg function is responsible for parsing this handshake message.

By setting the size of the user mapping data to 0x7fff, we end up with a unsigned integer overflow bug in SslTryS4U2Self. Let us observe what happens.

First we have a do while() loop. This iterates the provided user mapping data until a null-terminator is reach. After this loop rax_5 will be set to 0x7fff. Next we increment this value and then multiply by 2, but since we are dealing with the ushort data-type, we now end up with an unsigned integer overflow. This overflowed value is then saved to the r13 register. It does further down check if AuthenticationInfromationLength_1 is smaller then the maximum positive value for a signed int32, however, that does not stop the overflow bug.

And we then see this overflowed size being used in a call to memcpy:

What ends up happening now is memcpy just returning an empty array. Since we passed the overflowed 0-size to memcpy. So we basically end up with an empty field in the structure that ends up getting passed to LsaLogonUser. I have not been able to come up with a way to exploit this. Perhaps if an endpoint assumes that this field should be populated and doesn't perform a size check, then yes, maybe this could result in a bug. There is a handful of integer overflow bugs in Secure Channel which I never ended up reporting, because I believe the minimum bar to report a bug should be a crash PoC (Of-course don't report bugs to Microsoft either way, just drop them). However, I feel like this is a nice bug to start this blog series with. In the next blog post we will start digging much much deeper and attempt to find some bugs we can actually exploit.

Edit 2: I was thinking a little bit deeper about this bug last night, there may also be a path to exploit it if it is utilized during chain building in Secure Channel. Because then we may have an incongruence between what was used in Secure Channel and what ended up getting passed into LsaLogonUser. Definitely an angle worth exploring, but I will leave it to the reader as an exercise. Anytime you are able to perform any behavior at all, which wasn't intended by the developer, the next step is to explore every angle for abuse. Be it from a memory corruption perspective or logic perspective. Sometimes this yields nothing (most of the time), but sometimes you strike gold.

No comments:

Post a Comment

Note: only a member of this blog may post a comment.