Jekyll2023-05-18T07:54:59+00:00https://csandker.io//feed.xmlcsandker.ioMy personal blogActive Directory Spotlight: Attacking Microsoft’s Configuration Manager (SCCM/MECM)2023-05-17T08:00:00+00:002023-05-17T08:00:00+00:00https://csandker.io//2023/05/17/Active-Directory-Spotlight-Attacking-The-Microsoft-Configuration-Manager<p><em>I’ve published this blog post on my employeer’s blog. This entry is used just to align and keep track of the chronicle.</em></p>
<p>Read the blog post here:</p>
<blockquote>
<p><strong><a href="https://www.securesystems.de/blog/active-directory-spotlight-attacking-the-microsoft-configuration-manager/">https://www.securesystems.de/blog/active-directory-spotlight-attacking-the-microsoft-configuration-manager/</a></strong></p>
</blockquote>I’ve published this blog post on my employeer’s blog. This entry is used just to align and keep track of the chronicle.Offphish - Phishing revisited in 20232023-02-09T08:00:00+00:002023-02-09T08:00:00+00:00https://csandker.io//2023/02/09/Offphish-Phishing-revisited-in-2023<p><em>I’ve published this blog post on my employeer’s blog. This entry is used just to align and keep track of the chronicle.</em></p>
<p>Read the blog post here:</p>
<blockquote>
<p><strong><a href="https://www.securesystems.de/blog/offphish-phishing-revisited-in-2023/">https://www.securesystems.de/blog/offphish-phishing-revisited-in-2023/</a></strong></p>
</blockquote>I’ve published this blog post on my employeer’s blog. This entry is used just to align and keep track of the chronicle.Untangling Azure Active Directory Permissions II: Privileged Access2022-11-10T08:00:00+00:002022-11-10T08:00:00+00:00https://csandker.io//2022/11/10/Untangling-Azure-II-Privileged-Access<h2 class="no_toc toc-header" id="contents">Contents:</h2>
<ul id="markdown-toc">
<li><a href="#tldr" id="markdown-toc-tldr">TL;DR</a></li>
<li><a href="#intro" id="markdown-toc-intro">Intro</a></li>
<li><a href="#aad-access-concepts" id="markdown-toc-aad-access-concepts">AAD Access Concepts</a></li>
<li><a href="#privileged-access" id="markdown-toc-privileged-access">Privileged Access</a></li>
<li><a href="#azure-accesspermissions-v02" id="markdown-toc-azure-accesspermissions-v02">Azure-AccessPermissions v.0.2</a></li>
<li><a href="#adding-global-perspective" id="markdown-toc-adding-global-perspective">Adding Global Perspective</a></li>
</ul>
<h2 id="tldr">TL;DR</h2>
<p>After focusing highly on service principals in my <a href="/2022/10/19/Untangling-Azure-Permissions.html">previous post</a>, I went on to add Users and Groups into my enumeration script and ended up re-structuring and re-designing the entire thing. I’ve also focused on using my enumeration learnings to automate the process of identifying high privileged principals in an Azure Active Directory Tenant.<br />
If you came just for the tool, click <a href="#azure-accesspermissions-v02">here</a> for the fast track.</p>
<h2 id="intro">Intro</h2>
<p><em>A short note before I dive into the matter:<br />
If you haven’t already done so, I’d recommend reading through my <a href="/2022/10/19/Untangling-Azure-Permissions.html">previous post</a> first to not get lost with terminology and concepts</em>.</p>
<p>As detailed in said previous post, we already figured that there are basically only 3 types of principals that can have access to things:</p>
<ul>
<li>Users</li>
<li>Groups</li>
<li>Service Principals</li>
</ul>
<p>In determining “how someone can be granted access to something”, I drilled down into the concepts of <strong>AppRoles</strong>, <strong>Application</strong> and <strong>Delegated</strong> API Permissions and finally also into what <strong>Effective Permissions</strong> are. These are important building blocks in describing how a principal gets access to something. However, there are also other building blocks, which I did not cover in the previous post (cause these others are less confusing). This post aims to add these into the picture to get a more complete view.</p>
<h2 id="aad-access-concepts">AAD Access Concepts</h2>
<p>Okay so to begin with let’s wrap all of these things that were covered in the previous blog post into this simple statement:</p>
<blockquote>
<p><strong>A resource application can grant a User/Group/Service principal access to a certain resource or object either via Application-Type or via Delegation-Type API permissions.</strong></p>
</blockquote>
<p>Cool, let’s frame that visually:</p>
<p><img src="/public/img/2022-11-10-Untangling-Azure-II-Privileged-Access/Azure_Access_Controls_1.png" alt="First step in Azure Access Controls" title="First step in Azure Access Controls" /></p>
<p>This is pretty much were we left off with the previous post, so let’s add in those less-confusing building blocks to complete the picture.<br />
The next piece in the puzzle is the concept of <strong>Directory Roles</strong>, which you surely have already encountered with or without knowing the name for it. Ever heard of these <em>roles</em> in your Azure tenant?</p>
<ul>
<li>Global Administrator</li>
<li>Global Reader</li>
<li>Billing Administrator</li>
<li>Teams Administrator</li>
<li>…</li>
</ul>
<p>All of these are examples for a “Directory Role”. Putting that in a bit more technical terms one could say a <strong>Directory Role</strong> is a fixed set of permissions defined globally for your tenant. Let me draw a line here to on-premise Active Directory to emphasize the differences for this specific access building block between Active Directory and Azure Active Directory:</p>
<p>If you’re coming from an on-premise Active Directory home base, you will know the “Domain Administrators” and “Enterprise Administrators” groups. In (on-premise) Active Directory these are groups with high privilege permissions.<br />
In Azure Active Directory Microsoft wanted a similar experience of granting administrative users (high) privileges by connecting these users to a privileged container, but <strong>in Azure AD Microsoft wanted to decouple the concept of a group from the concept of a role</strong> (<em>my interpretation/assumption</em>). While a group is container that clusters users based on a specific attribute, a role is a privilege assignment that can be more ephemeral. A role can be added temporarily or only under specific conditions.</p>
<p>Long story short: A <strong>Directory Role</strong> is not a group in Azure AD, but an independent building block to capsulate access grants. Moreover there are default, already built-in directory roles, such as “Global Reader”, “Global Administrator”, etc., and there are also <strong>Custom Directory Roles</strong> that a high privileged user - more precisely a user that holds the “Privileged Role Administrator” or “Global Administrator” default directory role - can create to customize access to objects. These latter “custom directory roles”, however, come with a literal price tag, as they are only available in Azure Premium P1 or P2 licensed tenants.</p>
<p>Alright, let’s add those <strong>Directory Roles</strong> into the picture:</p>
<p><img src="/public/img/2022-11-10-Untangling-Azure-II-Privileged-Access/Azure_Access_Controls_2.png" alt="Second step in Azure Access Controls" title="Second step in Azure Access Controls" /></p>
<p>As you can see above, <strong>Directory Roles can only be assigned to Users and Groups</strong>, but not to Service Principals.<br />
As a side note: If you want to add a Directory Role to a group, you have to make that decision when you create the group. Only during group creation you can set <code class="language-plaintext highlighter-rouge">isAssignableToRole</code> property, which is then immutable. Moreover, <strong>the feature to assign a directory role to a group requires a Azure Premium P1 or P2 license</strong><br />
If this is your first encounter with default and custom directory roles, you want these resource links in your back as a lookup reference:</p>
<ul>
<li><a href="https://learn.microsoft.com/en-us/azure/active-directory/roles/permissions-reference">https://learn.microsoft.com/en-us/azure/active-directory/roles/permissions-reference</a></li>
<li><a href="https://learn.microsoft.com/en-us/azure/active-directory/roles/custom-create">https://learn.microsoft.com/en-us/azure/active-directory/roles/custom-create</a></li>
</ul>
<p>We’re getting close, there is one last building block to add - <em>at least to my current knowledge</em>, which is <strong>Ownership</strong>, where ownership grants control over an object.<br />
As there is no big secret in ownership, let’s add that right in:</p>
<p><img src="/public/img/2022-11-10-Untangling-Azure-II-Privileged-Access/Azure_Access_Controls_3.png" alt="Third and final step in Azure Access Controls" title="Third and final step in Azure Access Controls" /></p>
<p>While ownership is not a massively complex concept, it is not immediately obvious <strong>who can own what</strong>, so let me add a small table for <em>some</em> of the important Azure AD objects:</p>
<table>
<thead>
<tr>
<th style="text-align: center"><strong>Who\What</strong></th>
<th style="text-align: center"><strong>User</strong></th>
<th style="text-align: center"><strong>Group</strong></th>
<th style="text-align: center"><strong>Service Principal</strong></th>
<th style="text-align: center"><strong>Application</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center"><strong>User</strong></td>
<td style="text-align: center"><span style="color: #5b5a5a">No</span></td>
<td style="text-align: center"><b><span style="color: #5ba666">Yes</span></b></td>
<td style="text-align: center"><b><span style="color: #5ba666">Yes</span></b></td>
<td style="text-align: center"><b><span style="color: #5ba666">Yes</span></b></td>
</tr>
<tr>
<td style="text-align: center"><strong>Group</strong></td>
<td style="text-align: center"><span style="color: #5b5a5a">No</span></td>
<td style="text-align: center"><span style="color: #5b5a5a">No</span></td>
<td style="text-align: center"><span style="color: #5b5a5a">No</span></td>
<td style="text-align: center"><span style="color: #5b5a5a">No</span></td>
</tr>
<tr>
<td style="text-align: center"><strong>Service Principal</strong></td>
<td style="text-align: center"><span style="color: #5b5a5a">No</span></td>
<td style="text-align: center"><b><span style="color: #5ba666">Yes</span></b></td>
<td style="text-align: center"><span style="color: #5b5a5a">No</span></td>
<td style="text-align: center"><span style="color: #5b5a5a">No</span></td>
</tr>
<tr>
<td style="text-align: center"><strong>Application</strong></td>
<td style="text-align: center"><span style="color: #5b5a5a">No</span></td>
<td style="text-align: center"><span style="color: #5b5a5a">No</span></td>
<td style="text-align: center"><span style="color: #5b5a5a">No</span></td>
<td style="text-align: center"><span style="color: #5b5a5a">No</span></td>
</tr>
</tbody>
</table>
<p>Looking at this, four things should be noted here:</p>
<ol>
<li>No one can own a user object</li>
<li>Group and Application objects can’t own other objects</li>
<li>A service principal can own a group</li>
<li>This table does not contain all objects that can exists in an Azure Active Directory environment.</li>
</ol>
<p>Also the observant reader might have wondered why I included application objects in the table above, as applications aren’t principals that can access other things (as detailed in my <a href="/2022/10/19/Untangling-Azure-Permissions.html">previous post</a>).<br />
I’m glad you asked, there are two reasons for this:</p>
<ul>
<li>Looking at the table one should note that an application object can not own any other object, cause that would make no sense. An application object is not a ‘principal’ (in my terminology), but instead the service principal associated with the application object can own things. <em>But that is only a small side benefit of the actual reason, which is:</em></li>
<li>While an application object cannot own any other object it can be owned by other objects (e.g. Users) and on top of that <strong>an application object can have a different owner than its associated service principal</strong>. Meaning that a scenario could arise where you can’t control a service principal, but you can control its associated application object and thus could lead to privilege escalation opportunities towards the service principal object.</li>
</ul>
<p>Alright, that should be a good wrap to cover the building blocks for access permissions in Azure Active Directory. Now let’s turn to something more interesting…</p>
<h2 id="privileged-access">Privileged Access</h2>
<p>The reason I initially dived into all of this was because I wanted to figure who are the high privileged principals in an Azure Active Directory tenant.<br />
Similar to on-premise Active Directory environments this questions is not always trivial to answer, primarily due to two reasons:</p>
<ol>
<li>You have to figure out <strong>what makes someone ‘high privileged’</strong>, while considering all properties and conditions that could empower a principal.</li>
<li>Once you have carved out all the paths that could lead to high privileges, you have to walk all dependencies to other objects and principals to chain all principals with transitive and/or inherited privileges.</li>
</ol>
<p>As this is important for the following let me quickly pop two simple examples for these problems.</p>
<p>Problem 1 - What makes someone ‘high privileged’: The by far most obvious attribute that makes someone high privilege is if this someone holds a <strong>high privileged default directory role</strong>, such as ‘Global Administrator’. The problem is finding the not obvious attributes, for example: What about AppRoles? Which AppRoles could make someone high privileged?</p>
<p>Problem 2 - Transitive privileges: Imagine that by doing your magic you found a high privileged service principal. Before closing the book you have to answer if there is any principal that has control over the high privileged service user you just identified. If yes, then that someone must also be considered high privileged. In case a group has control over a privileged entity, then all members of that group might need to be considered high privileged and if that group contains other groups as members you might need traverse through all the nested group memberships and add all members as high privileged principals on the way…</p>
<p>As you might agree at this point: Things can get messy quickly. To not get totally lost and add some structure and guidance on our path to find high privileged principals, I’ll map out some visual paths that address the two problems from above. But before I’ll get into that, it is important to note two important constraints that went into my mapping process:</p>
<p>Ownership constraints: As said above not every object can be owned and not every principal can own an object. <strong>Most importantly a user object can own all other objects, but cannot be owned itself</strong>. This prevents circular dependencies which is good for our mapping process.</p>
<p>Delegation-Type permissions constraints: In my current understanding of the Azure AD permission jungle a delegation-type permission grant will result in <strong>effective access permissions consisting of the least privileged intersection</strong> of the privileges assigned from the delegation and the privileges initially hold by the requesting principal. Therefore delegation-type permissions should not allow any escalation of privileges or grant any privileged access that the receiving principal did not already hold anyways. In other words: A delegated permission grant can’t make you more privileged. Therefore I ignored delegation-type permissions in my mapping process.</p>
<p>Alright, that should be good enough. Let’s map out all the learnings from above and check how each principal can gain high privileges.</p>
<p><img src="/public/img/2022-11-10-Untangling-Azure-II-Privileged-Access/Azure_AD_high_privileged_access_map.png" alt="Azure Active Directory Privileged Access Map" title="Azure Active Directory Privileged Access Map" /></p>
<p>Studying this figure should also re-enforce some of the constraints mentioned above, and a few additional ones. For example you should see that:</p>
<ul>
<li>Groups can’t own other objects</li>
<li>Delegation-Type permission grants should not be relevant for mapping high privileged principals</li>
</ul>
<p>Aside from these constraints there are should be at least two open questions for anyone wanting to implement this.<br />
A.) Which are the high privileged (default) Directory Roles?<br />
B.) Which are the high privileged Application-Type AppRoles?</p>
<p>Glad you asked:</p>
<p>A.) I doubt there ever will be a definitive answer to this. Over time additional roles will be added and privilege escalation vectors will be discovered for various roles, which Microsoft will deem to be “by design”. Anyhow, as of writing this post I considered the 15 roles that Microsoft lists <a href="https://learn.microsoft.com/en-us/azure/active-directory/conditional-access/howto-conditional-access-policy-admin-mfa">here</a> to be high privileged.</p>
<p>B.) The same disclaimer from bevor also applies for AppRoles, however there is an additional problem here: Every App developer can freely chose any AppRole value, hence there are only two options to determine if a given AppRole grants high privileged access. Either one could hunt down all the ‘typical’ high privileged AppRoles that are present in <em>most</em> Azure ADs, like ‘Directory.ReadWrite.All’… or one could try to programmatically guess the potential privilege value based on the value of an AppRole. I’ll took the latter path:</p>
<div class="language-s highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">##</span><span class="w">
</span><span class="c1">## confidence level </span><span class="w">
</span><span class="c1">## 0 => Assumed Not high privilege</span><span class="w">
</span><span class="c1">## >0 => Assumed high privilege</span><span class="w">
</span><span class="c1">## 100 => Certainly high privilege</span><span class="w">
</span><span class="o">$</span><span class="n">confidenceLevel</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">0</span><span class="w">
</span><span class="n">If</span><span class="p">(</span><span class="w"> </span><span class="o">$</span><span class="n">AppRoleObject.Value</span><span class="w"> </span><span class="p">){</span><span class="w">
</span><span class="n">If</span><span class="p">(</span><span class="w"> </span><span class="o">$</span><span class="n">AppRoleObject.Value</span><span class="w"> </span><span class="o">-</span><span class="n">eq</span><span class="w"> </span><span class="s1">'Directory.ReadWrite.All'</span><span class="w"> </span><span class="p">){</span><span class="w">
</span><span class="o">$</span><span class="n">confidenceLevel</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">100</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="n">ElseIf</span><span class="p">(</span><span class="w"> </span><span class="o">$</span><span class="n">AppRoleObject.Value</span><span class="w"> </span><span class="o">-</span><span class="n">Like</span><span class="w"> </span><span class="s1">'*FullControl.All'</span><span class="w"> </span><span class="p">){</span><span class="w">
</span><span class="o">$</span><span class="n">confidenceLevel</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">10</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="n">ElseIf</span><span class="p">(</span><span class="w"> </span><span class="o">$</span><span class="n">AppRoleObject.Value</span><span class="w"> </span><span class="o">-</span><span class="n">Like</span><span class="w"> </span><span class="s1">'*ReadWrite.All'</span><span class="w"> </span><span class="p">){</span><span class="w">
</span><span class="o">$</span><span class="n">confidenceLevel</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">10</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="n">ElseIf</span><span class="p">(</span><span class="w"> </span><span class="o">$</span><span class="n">AppRoleObject.Value</span><span class="w"> </span><span class="o">-</span><span class="n">Like</span><span class="w"> </span><span class="s1">'full_access*'</span><span class="w"> </span><span class="p">){</span><span class="w">
</span><span class="o">$</span><span class="n">confidenceLevel</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">10</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="c1">## Return condifence level </span><span class="w">
</span><span class="n">return</span><span class="w"> </span><span class="o">$</span><span class="n">confidenceLevel</span><span class="w">
</span></code></pre></div></div>
<h2 id="azure-accesspermissions-v02">Azure-AccessPermissions v.0.2</h2>
<p>As said in the beginning I’ve put all my learnings into a re-worked version of my PowerShell Script. You can find that updated version here: <a href="https://github.com/csandker/Azure-AccessPermissions">Azure-AccessPermissions</a></p>
<p><img src="/public/img/2022-11-10-Untangling-Azure-II-Privileged-Access/Azure_Access_Permissions_v.0.2_Banner.png" alt="Azure-AccessPermissions.ps1 v.0.2" title="Azure-AccessPermissions.ps1 v.0.2" /></p>
<p><img src="/public/img/2022-11-10-Untangling-Azure-II-Privileged-Access/Azure_Access_Permissions_v.0.2_ShowCase.png" alt="Showcase of Azure-AccessPermissions.ps1 v.0.2" title="Showcase Azure-AccessPermissions.ps1 v.0.2" /></p>
<p>This update brings different enumeration methods for the three principal-types as well as two general enumeration functions, which <em>try</em> to dig out as many high privileged principals as I could find.<br />
The important disclaimer here is: I have no illusions that this code (or my current understanding) will cover all cases of hidden privileges. There surely are a ton more privileged edges that I haven’t thought about.</p>
<p>… Speaking of edges: Why not use <a href="https://github.com/BloodHoundAD/AzureHound">AzureHound</a> instead?<br />
You absolutely should (see also next section), I just wanted my own learning path. I’ve not compared the output results, overlaps or blind spots.</p>
<h2 id="adding-global-perspective">Adding Global Perspective</h2>
<p>You may have wondered what is with all the other Azure resources that you have read about in other blog posts, such as access to Azure VMs or what about read access to SharePoint sites and files, which might as well indicate high access privileges ?!</p>
<p>As of now I’ve focused my learning path solely on Azure Active Directory. To put that into a visual perspective, be aware that this is what I’ve talked above:</p>
<p><img src="/public/img/2022-11-10-Untangling-Azure-II-Privileged-Access/Azure_And_External_Cosmos.png" alt="Azure Active Directory In its global cosmos" title="Azure Active Directory In its global cosmos" /></p>
<p><img src="/public/img/2022-11-10-Untangling-Azure-II-Privileged-Access/lion_king_meme.jpg" alt="lion_king_meme" /></p>Contents:Untangling Azure Active Directory Principals & Access Permissions2022-10-19T08:00:00+00:002022-10-19T08:00:00+00:00https://csandker.io//2022/10/19/Untangling-Azure-Permissions<h2 class="no_toc toc-header" id="contents">Contents:</h2>
<ul id="markdown-toc">
<li><a href="#tldr" id="markdown-toc-tldr">TL;DR</a></li>
<li><a href="#intro" id="markdown-toc-intro">Intro</a></li>
<li><a href="#access-permission-breakdown" id="markdown-toc-access-permission-breakdown">Access Permission Breakdown</a> <ul>
<li><a href="#applications-and-enterprise-applications" id="markdown-toc-applications-and-enterprise-applications">Applications and Enterprise Applications</a></li>
<li><a href="#app-roles" id="markdown-toc-app-roles">App Roles</a></li>
<li><a href="#api-permissions" id="markdown-toc-api-permissions">API Permissions</a></li>
<li><a href="#application-and-delegated-api-permissions" id="markdown-toc-application-and-delegated-api-permissions">Application and Delegated API Permissions</a></li>
</ul>
</li>
<li><a href="#effective-access-permissions" id="markdown-toc-effective-access-permissions">Effective Access Permissions</a></li>
<li><a href="#applied-scenario-who-has-access-to-what" id="markdown-toc-applied-scenario-who-has-access-to-what">Applied Scenario: Who has access to what?</a></li>
<li><a href="#automation---introducing-azure-accesspermissionsps1" id="markdown-toc-automation---introducing-azure-accesspermissionsps1">Automation - Introducing: Azure-AccessPermissions.ps1</a></li>
<li><a href="#references" id="markdown-toc-references">References</a></li>
</ul>
<h2 id="tldr">TL;DR</h2>
<p><strong>I’ve released a PowerShell script to enumerate access permissions in an Azure AD tenant, if you just came for the tool, here’s your fast track: You can find the tool <a href="https://github.com/csandker/Azure-AccessPermissions">here</a>, see how it’s used and what the output looks like in the <a href="#automation---introducing-azure-accesspermissionsps1">last section</a>.</strong></p>
<h2 id="intro">Intro</h2>
<blockquote>
<p>All my live is learning in progress:<br />
If you spot something that is technically incorrect, misleading or unclear: <a href="https://twitter.com/0xcsandker">Let me know</a> about it and I’ll fix it.</p>
</blockquote>
<p>While preparing an Azure Active Directory engagement it took me a couple of days to untangle and structure the various actors in the game of “who has access to what” in Azure Active Directory, so I figured I’ll preserve my learnings for my older self and those that also need to get an (hopefully) easier start into this topic.</p>
<p>Before diving into the matter, let me quickly note a few things that I found difficult to understand, to share what might get people lost:</p>
<ul>
<li>The number one pain point for me was the lack of clear, uniformly naming and presentation schemes of different components, for example when comparing objects accessed through the Azure Portal with objects accessed via the <a href="https://github.com/microsoftgraph/msgraph-sdk-powershell">official Microsoft Graph PowerShell module</a>.</li>
<li>Additional when focusing only on a single source of access (Portal or PowerShell module) it’s hard to make sense of different attributes that all sound similar when looking for “who has access to this”.</li>
<li>Finally: When diving into these topics and reading documentation, it’s really hard to keep track of way to many acronyms, terms and concept/logic junctions that are introduced way to early.</li>
</ul>
<p>Enough whining, here’s my attempt to make thinks more understandable…</p>
<h2 id="access-permission-breakdown">Access Permission Breakdown</h2>
<p>Before anything else let’s define who are the actors in this game of “who has access to what”. Readers coming from an Active Directory (the on-premise thing) home base will know that not only your registered Users can have access to certain resources, but that also other identities exist that could have access to things. Therefore, an identity that can access something is usually not referred to as “User” (cause that would miss a few other actors), but as “Principal”. Using that notion there are 3x principals that can have access to things in Azure Active Directory:</p>
<ul>
<li>Users</li>
<li>Groups</li>
<li>Service Principals</li>
</ul>
<p>To keep everyone on track early on: We’ll have a closer look on what “Service Principals” are, but to make this term ‘usable’ right from the start here’s the quick preview:</p>
<blockquote>
<p>Service principals are entities that act on behalf of an application (e.g. Outlook). In a sense they represent an instantiated ‘user’ object bound to an application.</p>
</blockquote>
<p>To get a hold of what applications and service principals are, let’s assume we’re in an entirely empty Azure universe for a moment. We start to populate the environment by adding users and organize our users in groups. To get us productive and use our universe we’ll now add an application that will allow us to access and modify our environment.<br />
We could start with an application that everyone knows, like “Outlook”, but we don’t want to send/receive mails, instead we want to have a unified API to access all the components in Azure, so we’ll start with the application “Microsoft Graph” (choosing Microsoft Graph will have reference benefits later on). When speaking about “the application” (Microsoft Graph in this case), this application will in some documentations also be referenced to as “Resource application”. I like this notion for our case as it indicates that our application holds resources that we (and others) later want to access, for example our “Microsoft Graph” application holds information about user and group resources (you will see how this notion comes in handy later on).</p>
<p>We have coded the application and are now integrating it into our Azure environment, aka. in our Azure tenant. To do that we’re logging into the Azure Portal, head over to “Azure Active Directory” and click “App registrations”.</p>
<p><img src="/public/img/2022-10-19-Untangling-Azure-Principals/AppRegistration2.png" alt="Azure Portal App Registrations" title="Azure Portal App Registrations" /></p>
<p>When registering our “Microsoft Graph” application using this UI flow, we can set a name and a few other settings that are not too interesting for us in this moment. What is important though is that once you hit the “Register” button 2x new objects are created in your Azure tenant:</p>
<ul>
<li>An <strong>application object</strong> that represents your application (“Microsoft Graph”). This object holds all the app settings you made.</li>
<li>A <strong>service principal</strong> that acts as an ‘‘identity entity’ for your application. If your application needs to access other resources it is not the application object that is interacting with the other resource, it is your application’s service principal.</li>
</ul>
<p>While the application object serves as a technical container to represent your application (and all its settings), the application’s service principal primarily acts as identity principal in the sense that it can make access requests and has access rights.</p>
<p>Technically we could also impose these technical capabilities to the application object, so why do we need a second object?<br />
In our current scenario there is only our Azure tenant in the universe, but sooner or later other customers will join the cloud party and will have their own tenants and we want them to be able to use our application. In order to operate in another tenant, our application needs an identity within that other tenant that can access objects and do stuff. This is where our application’s service principal as a standalone identity comes in handy. While the application objects only lives in the tenant that registered the application (its ‘home tenant’) a (application) <strong>service principal is created in any tenant that installs the application</strong>. By using the concept of a “service principal” we have an individual object in all tenants that use our application, which shares some common attributes derived from our application object. These service principals allow us to set up and operate our application in any given tenant.</p>
<p>To summarize this is where we are at the moment:</p>
<p><img src="/public/img/2022-10-19-Untangling-Azure-Principals/AppObject-and-ServicePrincipal.png" alt="Relationship of Application Object and Service Principal" title="Relationship of Application Object and Service Principal in an Azure tenant" /></p>
<h3 id="applications-and-enterprise-applications">Applications and Enterprise Applications</h3>
<p>To recap and summarize from the previous section:</p>
<ul>
<li>Registering <em>your</em> application in <em>your</em> tenant results in an application object and a service principal object being created in <em>your</em> (home) tenant.</li>
<li>Installing an application of someone else in your tenant results in (only) a service principal being created in your tenant.</li>
</ul>
<p>This helps to understand the difference between “Applications” or “App registrations” and “Enterprise Applications” when browsing through the Azure portal. The Azure Portal menu item <strong>“App registration”</strong> will <strong>show the application objects</strong> of your tenant.<br />
The menu item <strong>“Enterprise applications”</strong> will <strong>show the service principals</strong> installed in your tenant.</p>
<p>This is why the blue help button under “App registrations” (see first screenshot of the previous section) suggest to search under “Enterprise applications” if you can’t find the application that you’re looking as: “Apps owned by other organizations won’t appear here [under ‘App registrations’]”.</p>
<p>But don’t fall into the “My objects are under ‘App registration’ and the objects of other organizations are under ‘Enterprise applications’” trap.<br />
Remember: <strong>Once you register your application within your tenant you will find the application object of <em>your</em> app listed under “App registrations” and the service principal of <em>your</em> app listed under “Enterprise applications”.</strong></p>
<p><img src="/public/img/2022-10-19-Untangling-Azure-Principals/AppObject-and-ServicePrincipal-Within-The-Azure-Portal.png" alt="Application Objects and Service Principals Within the Azure Portal" title="Application Objects and Service Principals Within the Azure Portal" /></p>
<h3 id="app-roles">App Roles</h3>
<p>Okay so far so good, we understood application objects and (application) service principals. For the next part of the puzzle let’s refer back to our <strong>resource application</strong>. Remember how we introduced this notion to indicate that our application holds resources (users, groups) that we’d like to access. So far, we have not specified any boundaries for that access in the sense of permissions, e.g. are all our service principals allowed to read and write to all the resources in our application? Certainly not. We want fine grained access control and that is what “AppRoles” are for.</p>
<p><strong>An “AppRole” is an access permission grant defined as string</strong>, for example in the form of this: <code class="language-plaintext highlighter-rouge">User.ReadBasic.All</code><br />
This AppRole is meant to express that whoever holds this “AppRole” should be allowed to <em>read</em> <em>basic information</em> (e.g. names) of <em>all</em> user objects from our resource application. As <a href="https://twitter.com/_wald0">Andy Robbins</a> puts it in his <a href="https://medium.com/specter-ops-posts/azure-privilege-escalation-via-azure-api-permissions-abuse-74aee1006f48">great blog post</a>, these “AppRole” permissions are usually expressed in the following format: <code class="language-plaintext highlighter-rouge">Resource.Operation.Constraint</code></p>
<p>Alright, let’s add that to the picture:</p>
<p><img src="/public/img/2022-10-19-Untangling-Azure-Principals/ApplicationObjects-ServicePrincipals-And-AppRoles.png" alt="Application Objects, Service Principals and AppRoles" title="Application Objects, Service Principals and AppRoles" /></p>
<p>To make an “AppRole” more understandable, AppRoles are not defined as simple string attributes, but are contained in a separate object where you can also add in a more user-friendly display name, a description and of course a unique ID (AppRoleId), in order to reference a specific AppRole.<br />
We can find all of this in the Azure Portal under “App registrations” » <YourApplication> » “App Roles”:</p>
<p><img src="/public/img/2022-10-19-Untangling-Azure-Principals/AppRoles-In-The-Azure-Portal.png" alt="Defined AppRoles in the Azure Portal" title="Defined AppRoles in the Azure Portal" /></p>
<h3 id="api-permissions">API Permissions</h3>
<p>Everyone that had browsed to the “App registration” view in a quest to define access rights to their application must have wondered at some point what the “API permissions” menu item is for, which is also contained in the same menu blade that contains the “App roles”. At least this menu item does sound similar in regards to granting access to…something…</p>
<p>The idea here is that while under <strong>“App roles”</strong> you can <strong>define access definitions to your own application</strong>.<br />
The menu item <strong>“API permissions”</strong> on the other hand allows you to <strong>set</strong> in <strong>permissions</strong> for <em>your</em> application <strong>to access <em>other</em> public APIs</strong>.<br />
This is the scene where you say “My application needs to access data from another resource application (managed within Azure) and I want to allow my application to do that”. If you’re now thinking: “My application is accessing something else, aka. making an authenticated access requests – that sounds like something the service principal of my application is responsible for”, then you are right on track. That is exactly what the service principal of your application is for.</p>
<p>In order to make this scenario a bit more visual we will leave our previous scenario of managing the “Microsoft Graph” application and will now take a second seat where we are an ordinary company that has developed and registered their favorite “SampleApp” application into their Azure (home) tenant. In order for our “SampleApp” to function properly we need to query basic user information in the tenants of our clients. We decide the best way to do this is by querying a public Microsoft API and as the “Azure AD Graph API”, which had been the previous data authority, will be <a href="https://techcommunity.microsoft.com/t5/microsoft-entra-azure-ad-blog/azure-ad-change-management-simplified/ba-p/2967456?WT.mc_id=M365-MVP-9501">deprecated by the end of 2022</a>, we want to use the new Azure data authority, the “Microsoft Graph API”.</p>
<p>In order to do that we click on “API Permissions” within the “App registration” view and then click on “Add a permission”, you can see that flow and the result below:</p>
<p><img src="/public/img/2022-10-19-Untangling-Azure-Principals/Setting-API-Permissions-In-The-Azure-Portal.png" alt="Setting API Permissions in the Azure Portal" title="Setting API Permissions in the Azure Portal" /></p>
<p>Having a close look to the shown API Permissions we can spot that we don’t just specify broad access to the Microsoft Graph Resource application, but instead we use the exact “AppRole” definitions that have been specified in the previous section. These “AppRoles” of the Microsoft Graph resource are now used by our “SampleApp” to specify what access we need.</p>
<p><img src="/public/img/2022-10-19-Untangling-Azure-Principals/API-Permissions-Between-Two-Applications-In-Azure.png" alt="API Permissions Access in Azure" title="API Permissions Access in Azure" /></p>
<p>This would be a great spot to close the book, check off Azure access permissions and go for beers, but Microsoft won’t let us leave just yet as there is more to uncover…</p>
<h3 id="application-and-delegated-api-permissions">Application and Delegated API Permissions</h3>
<p>When defining API Permissions for our “SampleApp” - as we just did - Azure will ask us what “type” of permissions we want to set, before we can chose <em>what</em> (in terms of AppRoles) we want to have access to.<br />
There are two different types of API permissions:</p>
<ul>
<li>Application permissions</li>
<li>Delegated permissions</li>
</ul>
<p><img src="/public/img/2022-10-19-Untangling-Azure-Principals/Choosing-The-API-Permission-Type-In-The-Azure-Portal.png" alt="Choosing the API Permission Type in the Azure Portal" title="Choosing the API Permission Type in the Azure Portal" /></p>
<p>A brief explanation to what these two types is given by Microsoft in the screenshot above. I’ll try to add a bit more context to this:</p>
<p>Imagine our “SampleApp” would be a statistics application that shows basic user statics in our Azure dashboard, therefore our application would need to query basic user data from the Microsoft Graph API. Our application - and to be precise here: the service principal of our application - would need to directly access Microsoft Graph, saying: “Hey I’m the SampleApp I need some user data of my tenant users”. In this case we would chose <strong>“Application permissions”</strong> as our permission type, as our application (technically our application service principal) is the entity that will access the Microsoft Graph API.</p>
<p>To paint the opposite picture: Imagine our SampleApp to be a document storage application (like Sharepoint), that needs to read and write user (and other) data from the tenant where our application is installed. Again, we chose to query the Microsoft Graph API to retrieve or place data into the tenant, but in this case we want the access permissions to be dependent on the user that initiated an action (e.g. reading/writing a file). We don’t want that our application service principal is generally allowed to read/write all files of all users, but that the access is actually dependent on the user that uses our application (aka is logged in to our application). In Microsoft’s terminology our application should act on behalf of the logged-in user, with delegated permissions. Therefore, we choose <strong>“Delegated permissions”</strong> as our permission type for this scenario.</p>
<p>Knowing about these two API permission types finally completes the access picture:</p>
<p><img src="/public/img/2022-10-19-Untangling-Azure-Principals/API-Permissions-Between-Two-Applications-With-Permission-Type-In-Azure.png" alt="API Permissions Access with Permission Type in Azure" title="API Permissions Access with Permission Type in Azure" /></p>
<h2 id="effective-access-permissions">Effective Access Permissions</h2>
<p>The concept of “Delegated access permissions” brings up a new “problem” that needs to be handled.<br />
What if a user has been granted a permissions that does not match with the API permissions we just assigned to our application - more specifically to our application service principal.<br />
Example: What if we assigned our application service principal “User.ReadBasic.All” permissions, but the user that is logged-in to our application is a ‘Global Administrator’ in the current tenant and would thereby have much broader permissions within the Microsoft Graph API?</p>
<p>In an “on-premise” Active Directory environment this problem is solved through impersonation: A service impersonates a user and thereby takes all of the users access permissions and acts towards the resource as if being the user.</p>
<p>In Azure things are different. <strong>In Azure neither the user’s, nor the service principal’s access permissions take precedence</strong>, instead <strong>the least privileged intersection</strong> between those permission sets is build and access is then granted or denied based on this intersection set. This set of permissions that is finally evaluated by the resource application is what is called <strong>“effective permission”</strong>.</p>
<p>Okay, so when evaluating “delegated-type” permissions these resulting “effective permissions” are based on the user’s and service principal’s permissions. But what happens when evaluating “application”-type permissions?<br />
As mentioned before “application”-type permissions define access scenarios in which there is no “logged-on” user and the service principal acts on its own. In this case the “effective permissions” are equal to the service principal’s “application”-type permissions.</p>
<p>The figure below tries to visualize this concept:</p>
<p><img src="/public/img/2022-10-19-Untangling-Azure-Principals/Effective-API-Permissions-In-Azure.png" alt="Azure's Effective API Permissions" title="Azure's Effective API Permissions" /></p>
<p>If you’re still puzzled on this concept and want to read this in another voice, I recommend a read through the <a href="https://learn.microsoft.com/en-us/graph/auth/auth-concepts#delegated-and-application-permissions">official documentation</a> of these concepts (there are also examples given).</p>
<h2 id="applied-scenario-who-has-access-to-what">Applied Scenario: Who has access to what?</h2>
<p>To finally answer the question of “who has access to what”, let’s re-walk through our scenario from above to get an understanding of what steps are necessary for a principal to get access to a resource.</p>
<p>Let’s get back to where we started from: We have coded the “Microsoft Graph” resource application and are now about to add this to our Azure tenant. We log into the Azure Portal, browse to the Azure Active Directory Menu, click “App registrations”, register our app and define all the AppRoles that we need for Microsoft Graph.<br />
<strong>At this point in time no one has access to our application, as we haven’t assigned any permissions yet, we only defined access identifiers (AppRoles) to our application.</strong></p>
<p>As we clicked “Register”, our Microsoft Graph application object and the corresponding service principal have been created in our tenant. We’re now switching over to the “Enterprise applications” view, to inspect our newly created service principal.<br />
For testing purposes we now click on “Users and Groups” and add one of our tenant users to our “enterprise application”, which technically is our service principal. The UI will ask us which user we want to select, and which (App) role we want to assign to this user. We select any random user and any sample AppRole that we created in the previous step.</p>
<p><img src="/public/img/2022-10-19-Untangling-Azure-Principals/Azure-Portal-Add-User-To-Enterprise-Application.png" alt="Adding a User to an Azure Enterprise Application/Service Principal" title="Adding a User to an Azure Enterprise Application/Service Principal" /></p>
<p>This user has now been granted to access the application using the defined AppRole, the resource application (Microsoft Graph in this case) is responsible for checking this AppRole when accessing a record. In this example I created and assigned the AppRole “User.ReadBasic.All”, which - if this would be really the Microsoft Graph API - would allow the chosen user in our local tenant to access basic user information, e.g. names, from all our tenant users.<br />
<strong>At this point in time the user shown in the screenshot above has reading access permissions to the Microsoft Graph API</strong>.</p>
<p>To continue experimenting we’re now switching into our seat, where we have registered our “SampleApp” application. We head over to the “App registrations” view within the Azure Portal, click on “API permissions” and add a new API permissions for our SampleApp to allow access to Microsoft Graph. We choose “application”-type permissions (as for this first case we want only the service principal to have access) and set the permission (AppRole) once again to “User.BasicRead.All”.</p>
<p><img src="/public/img/2022-10-19-Untangling-Azure-Principals/Setting-API-Permissions-In-Azure-Portal-For-MS-Graph.png" alt="Setting API Permissions for our SampleApp in the Azure Portal" title="Setting API Permissions for our SampleApp in the Azure Portal" /></p>
<p>Once we click “Add permissions” the selected AppRole permissions are added for our SampleApp service principal, but as of now the service principal can’t use these permissions (yet), because for application-type permissions this AppRole requires “Admin consent”. That means an administrator has to explicitly allow this service principal to use these API permissions. Note the column that says “Admin consent required” and the orange warning sign in the status column.<br />
If we connect to Microsoft Graph using this service principal in the current state we will be allowed to connect, but the ‘Scope’ of our access key will be empty, which results in no permissions for our service principal.</p>
<p><img src="/public/img/2022-10-19-Untangling-Azure-Principals/MSGraph-Connection-Using-Service-Principal-Without-Admin-Consent.png" alt="Connecting to Microsoft Graph without Admin Consent" title="Connecting to Microsoft Graph without Admin Consent" /></p>
<p>Once we grant admin consent to these permissions through the “Grant admin consent” button, the UI shows a green checkmark in the status column, indicating that our service principal now holds the permissions we specified.<br /></p>
<p><img src="/public/img/2022-10-19-Untangling-Azure-Principals/Granting-Admin-Consent-In-The-Azure-Portal.png" alt="Granting Admin Consent in the Azure Portal" title="Granting Admin Consent in the Azure Portal" /></p>
<p>Reconnecting to Microsoft Graph using PowerShell will now result in a token with the granted access permissions.<br />
<em>Note: I changed the AppRole here to ‘User.Read.All’ in order to be able to read all user data, just for the sake of the demo.</em></p>
<p><img src="/public/img/2022-10-19-Untangling-Azure-Principals/MSGraph-Connection-Using-Service-Principal-With-Admin-Consent.png" alt="Connecting to Microsoft Graph with Admin Consent" title="Connecting to Microsoft Graph with Admin Consent" /></p>
<p><strong>At this point in time the service principal of our SampleApp has the effective permissions to read all the data of all the users in our tenant.</strong></p>
<p>Lastly, let’s remove these application-type permissions and instead set in delegated-type permission to ‘User.ReadBasic.All’:</p>
<p><img src="/public/img/2022-10-19-Untangling-Azure-Principals/Setting-Delegated-API-Permissions-In-Azure-Portal-For-MS-Graph.png" alt="Setting Delegated-Type API permissions for the Microsoft Graph Application" title="Setting Delegated-Type API permissions for the Microsoft Graph Application" /></p>
<p>The important difference to note here is the “Delegated” keyword in the “Type” column and that no admin consent is required for this API permission. Although it is the same AppRole (‘User.ReadBasic.All’), no admin consent is needed to acquire these permissions.</p>
<p><strong>At this point in time no user in our tenant has (yet) requested these permissions (and therefore no one holds these permissions), but any user could from now on.</strong></p>
<p>If a user signs in to the SampleApp and attempts to access Microsoft Graph the following consent prompt will be shown to the user:</p>
<p><img src="/public/img/2022-10-19-Untangling-Azure-Principals/PowerShell-User-Consent-Prompt.png" alt="User Consent Prompt to allow API Permissions" title="User Consent Prompt to allow API Permissions" /></p>
<p>Once the user accepts these consent prompt the user has obtained the specified, delegated permissions.<br />
We can double check and confirm which users have obtained these permissions in the “Enterprise application” view in the Azure Portal under the “User Consent” tab within the “Permissions” menu item, as shown below:</p>
<p><img src="/public/img/2022-10-19-Untangling-Azure-Principals/Viewing-Granted-Delegated-API-Permissions-In-The-Azure-Portal.png" alt="Showing Granted Delegated API Permissions in the Azure Portal" title="Showing Granted Delegated API Permissions in the Azure Portal" /></p>
<h2 id="automation---introducing-azure-accesspermissionsps1">Automation - Introducing: Azure-AccessPermissions.ps1</h2>
<p>I’ve build a PowerShell script to automate the learnings from above. You can find it <a href="https://github.com/csandker/Azure-AccessPermissions">here</a>.<br />
This is what the output looks like:</p>
<p><img src="/public/img/2022-10-19-Untangling-Azure-Principals/Azure-AccessPermissionsPS1.png" alt="Output of Azure-AccessPermissions.ps1" title="Output of Azure-AccessPermissions.ps1" /></p>
<h2 id="references">References</h2>
<ul>
<li><a href="https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent">Microsoft: Permissions and consent in the Microsoft identity platform</a></li>
<li><a href="https://learn.microsoft.com/en-us/graph/api/overview?view=graph-rest-1.0">Microsoft: Microsoft Graph REST API v1.0 endpoint reference</a></li>
<li><a href="https://learn.microsoft.com/en-us/powershell/microsoftgraph/azuread-msoline-cmdlet-map?view=graph-powershell-beta#applications">Microsoft: Azure AD to Microsoft Graph PowerShell cmdlets</a></li>
<li><a href="https://posts.specterops.io/azure-privilege-escalation-via-azure-api-permissions-abuse-74aee1006f48">SpecterOps: Azure Privilege Escalation via Azure API Permissions Abuse by @_Wald0</a></li>
</ul>Contents:Active Directory Spotlight: Windows Event Forwarding & Windows Event Collector2022-07-22T08:00:00+00:002022-07-22T08:00:00+00:00https://csandker.io//2022/07/22/Active-Directory-Spotlight-Windows-Event-Forwarding-And-Windows-Event-Collector<p><em>I’ve published this blog post on my employeer’s blog. This entry is used just to align and keep track of the chronicle.</em></p>
<p>Read the blog post here:</p>
<blockquote>
<p><strong><a href="https://www.securesystems.de/blog/active-directory-spotlight-windows-event-forwarding-windows-event-collector/">https://www.securesystems.de/blog/active-directory-spotlight-windows-event-forwarding-windows-event-collector/</a></strong></p>
</blockquote>I’ve published this blog post on my employeer’s blog. This entry is used just to align and keep track of the chronicle.Debugging and Reversing ALPC2022-05-29T08:00:00+00:002022-05-29T08:00:00+00:00https://csandker.io//2022/05/29/Debugging-And-Reversing-ALPC<h2 class="no_toc toc-header" id="contents">Contents:</h2>
<ul id="markdown-toc">
<li><a href="#introduction--disclaimer" id="markdown-toc-introduction--disclaimer">Introduction & Disclaimer</a></li>
<li><a href="#environment-preparation" id="markdown-toc-environment-preparation">Environment Preparation</a></li>
<li><a href="#getting-off-the-ground" id="markdown-toc-getting-off-the-ground">Getting Off The Ground</a></li>
<li><a href="#from-user-to-kernel-land" id="markdown-toc-from-user-to-kernel-land">From User to Kernel Land</a></li>
<li><a href="#hunting-an-alpc-object" id="markdown-toc-hunting-an-alpc-object">Hunting An ALPC Object</a></li>
</ul>
<h2 id="introduction--disclaimer">Introduction & Disclaimer</h2>
<p>This post is an addendum to my journey to discover and verify the internals of ALPC, which I’ve documented in <a href="/2022/05/24/Offensive-Windows-IPC-3-ALPC.html">Offensive Windows IPC Internals 3: ALPC</a>. While preparing this blog I figured a second post, explaining the debugging steps I took to verify and discover ALPC behaviour, could be useful to all of us that are beginners in the field of reverse engineering and/or debugging.</p>
<p>While I’ve certainly used the techniques and methods shown in this post below, these where not my only resources and tools to dive into ALPC. Even implying this would undermine the important and major work of other researchers that have documented and reversed ALPC internals in the past, like <a href="twitter.com/aionescu">Alex Ionescu</a> and <a href="/2022/05/24/Offensive-Windows-IPC-3-ALPC.html#references">many others</a>. Hence this disclaimer.<br /></p>
<p>TL;DR: The techniques below are practical and useful, but I was only able to apply them due to the work of others.</p>
<p>Another important disclaimer is: I am - by no means - an experienced reverse engineer and this blog post is not meant to be an introduction into ‘how to become a reverse engineer’ or show a smart way to get in this field. <strong>This is a ‘use Windows debugging to stumble into a topic and make your way to look around’ post</strong>.</p>
<h2 id="environment-preparation">Environment Preparation</h2>
<p>In order to follow the steps shown below you want to set up a kernel debugging environment. If you already have a kernel debugging environment set up, feel free to skip to section <a href="#getting-off-the-ground">Getting Off The Ground</a>. If you don’t, you’ve got two basic choices for this setup:</p>
<ul>
<li>Local live kernel debugging</li>
<li>Remote kernel debugging</li>
</ul>
<p>Although the local kernel debugging option only requires a single test machine (virtual VM) and only a single command and a reboot to set you up, I nevertheless recommend starting two machines (VMs) and set up for remote debugging. The reason for this is that local live kernel debugging comes with some constrains and you can’t use the full debugging feature set and can’t go all routes. I’ll nevertheless include the steps to set up local kernel debugging as well, in case you only have a single machine at hand in your test environment.<br /></p>
<p><strong>Setup local kernel debugging</strong><br />
The following steps needs to be done:</p>
<ol>
<li>Start up your testing machine or VM</li>
<li>If you do not already have <a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/debugger-download-tools">WinDbg</a> installed, download and install the WindowsSDK from <a href="https://developer.microsoft.com/en-us/windows/downloads/windows-sdk/">here</a> to install WinDbg.<br />
<em>Alternatively you can also use the WinDbg Preview from the Windows Store App.</em></li>
<li>Open up PowerShell with administrative privileges and run the following command to enable local kernel debugging: <code class="language-plaintext highlighter-rouge">PS:> bcdedit /debug on & bcdedit /dbgsettings local</code></li>
<li>Reboot your machine</li>
<li>Open up WinDbg and enter local kernel debugging mode by running the following command: <code class="language-plaintext highlighter-rouge">.\windbg.exe -kl</code><br />
<em>Alternatively you can also open up the WinDbg GUI, click File » Kernel Debug (Ctrl+K) » Local (Tab) » Ok</em></li>
</ol>
<p><img src="/public/img/2022-05-29-Debugging-and-Reversing-ALPC/WinDbg_LocalKernelDebugging.png" alt="WinDbg Local Kernel Debugging" title="Local Kernel Debugging with WinDbg" /></p>
<p><strong>A note about the customized layout shown above</strong><br />
In my case I like to have my debugging windows positioned and aligned in a certain way (and also have the colors mimic a dark theme). You can do all of that by starting WinDbg, open up and position all Windows the way you like them, change the coloring (if you want) under <em>View » Options » Colors</em> and finally save all your Workspace setup via <em>File » Save Workspace to File</em>. Once done, you can open up your local kernel debugging WinDbg with your customized Workspace as follows: <code class="language-plaintext highlighter-rouge">.\windbg.exe -WF <Path-To-File>.WEW -kl</code><br />
All WinDbg command line switches can be found <a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/windbg-command-line-options">here</a></p>
<p><strong>Setup remote kernel debugging</strong><br /></p>
<ol>
<li>Start your first testing machine or VM that you want to debug, this will be referred to as <em>debuggee</em> machine.</li>
<li>If you do not already have <a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/setting-up-a-network-debugging-connection-automatically">kdnet.exe</a> installed, download and install the WindowsSDK from <a href="https://developer.microsoft.com/en-us/windows/downloads/windows-sdk/">here</a> to install it.</li>
<li>Open up PowerShell with administrative privileges and run the following command: <code class="language-plaintext highlighter-rouge">cd "C:\\Program Files (x86)\\Windows Kits\\10\\Debuggers\\x64\\\" && .\kdnet.exe <DEBUGER-IP> <RandomHighPortNumber>'</code><br />
<em>I usually use *51111</em> as port number. This command will give you command line instructions to use from your debugger, see step 6.*</li>
<li>Start your second testing machine or VM that you want to use to debug your first VM, this will be referred to as <em>debugger</em> machine.</li>
<li>If you do not already have <a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/debugger-download-tools">WinDbg</a> installed, download and install the WindowsSDK from <a href="https://developer.microsoft.com/en-us/windows/downloads/windows-sdk/">here</a> to install it.<br />
<em>Alternatively you can also use the WinDbg Preview from the Windows Store App.</em></li>
<li>Run the following command to start WinDbg and attach it to your <em>debuggee</em> machine: <code class="language-plaintext highlighter-rouge">cd "C:\\Program Files (x86)\\Windows Kits\\10\\Debuggers\\x64\\" && .\windbg.exe -k <PASTE-OUTPUT-FROM-kdnet.exe-FROM-YOUR-DEBUGGEE></code>.<br />
<em>The command to paste from kdnet.exe (Step 3.), will look something like this:</em> <code class="language-plaintext highlighter-rouge">net:port=<YOUR-RANDOM-PORT>,key=....</code><br />
You will see a prompt indicating that the debugger is set up and is waiting to be connected.</li>
<li>Reboot the <em>debuggee</em> machine. Switch back to your <em>debugger</em> machine, which will connect during the boot process of your <em>debuggee</em>.</li>
</ol>
<p>You may have noted that I’ve mentioned the <em>WinDbg Preview</em> store app as an alternative to the classic WinDbg debugger. This preview version is a facelift version of the classic debugger and comes with quite a different UI experience (including a built-in dark-theme). If you’re looking at a one-time setup and are not emotionally attached to the old/classic WinDbg I encourage you to try the WinDbg Preview. The only reason I’m not using it yet is due to the fact that you can’t export your Workspace setup (window layout), which is a crucial feature for me in my lab (which i rebuild frequently).<br />
As a result of that I will be using classic WinDbg in the below</p>
<p><strong>Setting up symbols</strong><br />
Once you’ve setup WinDbg the last preparation step you’ll need to take is to setup your debugger to pull debugging symbols form Microsoft’s official symbol server.</p>
<p>Run the following set of commands within WinDbg to set up symbols:</p>
<ol>
<li>Within WinDbg run <code class="language-plaintext highlighter-rouge">.sympath</code> to show your current symbol path configuration.<br />
If it looks similar to the below, which specifies that you want your symbols to be loaded from Microsoft’s symbol server and cache those in C:\Symbols, you’re good to go…
<img src="/public/img/2022-05-29-Debugging-and-Reversing-ALPC/Windbg_sympath.png" alt="WinDbg sympath" title="WinDbg .sympath check" /></li>
<li>If your output does not look like this and you simply want to pull all your symbols from Microsoft’s official symbol server, run the following command within WinDbg: <code class="language-plaintext highlighter-rouge">.sympath srv*https://msdl.microsoft.com/download/symbols</code></li>
</ol>
<p>More about symbol servers, caching and the how & why can be found in Microsoft’s documentation page <a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/symbol-path#using-a-symbol-server">here</a>.</p>
<h2 id="getting-off-the-ground">Getting Off The Ground</h2>
<p>Let’s say we know nothing at all about ALPC and want to start digging and understanding how ALPC works under the hood. As ALPC is undocumented we cannot start our journey by sticking our head into Microsoft’s rich documentation catalogues, but instead we have to a apply a methodology that is based on a loop of reversing, making assumptions, testing assumptions and verification/falsification of assumptions to finally build our picture of ALPC.</p>
<p>Alright, if we do not know anything about a technology beside its name (ALPC), we can firing up our WinDbg kernel debugger and start to get some information about it by resolving function calls that contain the name “ALPC” - this might not be the smartest starting point, but that doesn’t matter, we start somewhere and make our way…<br />
The WinDbg command we need for this is: <code class="language-plaintext highlighter-rouge">kd:> x *!*Alpc*</code></p>
<p><img src="/public/img/2022-05-29-Debugging-and-Reversing-ALPC/ALPC_Function_List.png" alt="ALPC function list" title="Listing ALPC functions" /></p>
<p>This command will resolve function names of the following pattern <code class="language-plaintext highlighter-rouge">[ModuleName]![FunctionName]</code>, where we can use wildcards (‘*’) for both the module and function names. In this case that means we’re resolving all functions that contain the word “Alpc” in their names within all loaded modules.<br />
In case it’s your first time with WinDbg (or you’re like me and tend to forget what certain commands mean), you can always use WinDbg’s help menu to lookup a command via: <code class="language-plaintext highlighter-rouge">kd:> .hh [Command]</code>, as shown below:</p>
<p><img src="/public/img/2022-05-29-Debugging-and-Reversing-ALPC/WinDbg_help_menu.png" alt="WinDbg help menu " title="WinDbg's help menu" /></p>
<p><em>Side note: Although the command you’ve entered is pre-selected you actually have to click the ‘Display’ button. Another option is to lookup the Debugger commands online <a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/commands">here</a>.</em></p>
<p>If you get an error saying that that something could not be resolved, you likely do not have your symbol path set up. Ensure you have your symbols either stored locally or pulling from <em>https://msdl.microsoft.com/download/symbols</em> (or both). You can check your sympath with: <code class="language-plaintext highlighter-rouge">.sympath</code></p>
<p><img src="/public/img/2022-05-29-Debugging-and-Reversing-ALPC/Windbg_sympath.png" alt="Windbg sympath" title="WinDbg .sympath command" /></p>
<p>If you have your symbol path setup correctly, you’ll receive a good amount of results showing all sorts of functions that contain the name “ALPC”. If things take too long (because you made a typo, or things can’t be resolved or any other problem occurs) you can always hit <em><CTRL>+<Break></em> or open the <em>Debug</em> menu and click <em>Break</em> to stop the current action:</p>
<p><img src="/public/img/2022-05-29-Debugging-and-Reversing-ALPC/WinDbg_Break.png" alt="WinDbg break" title="WinDbg's 'Break' command" /></p>
<p>From here you should copy all the resolved functions into an editor of your choice (I use <a href="https://code.visualstudio.com/">VisualStudio Code</a>) and sort these by name to get a feeling for which Alpc functions exists in which modules and may belong to which components. The strong naming convention applied to the Windows codebase will help you a lot here, so let’s have a look at this:</p>
<p><img src="/public/img/2022-05-29-Debugging-and-Reversing-ALPC/Windows_FunctionNamingConvention.png" alt="Windows function naming convention" title="Example of function naming convenction in Windows" /></p>
<p>To make this more readable:</p>
<pre>
00007ff9`49498c54 >> The function address
ntdll >> The module name ("ntddl" in this case)
! >> The seperator
Tp >> Abbreviation of the component ("Thread Pool" in this case)
p >> Abbreviation of the function type ("private")
AllocAlpcCompletion >> Descriptive name of the functions
</pre>
<p>Looking only at this very first resolved function call we can make the assumption that this function is a <strong>private</strong> function within the <strong>ThreadPool</strong> component within <strong>ntdll.dll</strong>, which likely does some <strong>allocation</strong> of some memory for something.</p>
<p>Applying this knowledge to all listed functions, we can sort and organize the resolved functions to create a rough picture of where (in the codebase) these are implemented:</p>
<p><img src="/public/img/2022-05-29-Debugging-and-Reversing-ALPC/Alpc_Functions_Sorted.png" alt="Alpc functions sorted" title="Sorted ALPC functions" /></p>
<p>The value of this step is not being a 100 percent accurate or getting a label assigned to each function, but instead create a rough mapping of which parts of the OS are concerned with ALPC and which of these modules and function names sound familiar and which don’t.</p>
<p>From here on we can drill down into modules that sound familiar (or interesting) to us. For example we have spotted the <code class="language-plaintext highlighter-rouge">ntdll</code> module, which we know is the userland border gateway for calling native system (kernel) services (functions). So we can assume that Windows allows userland processes to call certain ALPC functions, which comes down the the assumption of “ALPC is usable from userland applications”.<br />
Looking only at “*Alpc*” functions inside the <em>ntdll</em> module we can find that there are 4 types of functions:</p>
<ul>
<li>No-component functions, e.g.: <code class="language-plaintext highlighter-rouge">ntdll!AlpcRegisterCompletionList</code></li>
<li>Nt-component functions, e.g.: <code class="language-plaintext highlighter-rouge">ntdll!NtAlpcCreateResourceReserve</code></li>
<li>Zw-component functions, e.g.: <code class="language-plaintext highlighter-rouge">ntdll!ZwAlpcCreateResourceReserve</code></li>
<li>Tp-Component functiosn, e.g.: <code class="language-plaintext highlighter-rouge">ntdll!TppAllocAlpcCompletion</code></li>
</ul>
<p>As the <em>Nt</em> and <em>Zw</em> functions are meant to call the same kernel functions (see <a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/libraries-and-headers">here</a>, <a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/what-does-the-zw-prefix-mean-">here</a> and <a href="https://stackoverflow.com/questions/4770553/windows-native-api-when-and-why-use-zw-vs-nt-prefixed-api-calls">here</a> for why they exist), we can safely ignore one them, so we’ll cut off the Zw functions. I myself am not too familiar with the thread pool manager, so I’ll drop the <em>Tp</em> functions as well, which leaves us with a much smaller set of potentially interesting functions:</p>
<p><img src="/public/img/2022-05-29-Debugging-and-Reversing-ALPC/Alpc_Selected_Ntdll_Functions.png" alt="Alpc selected ntdll functions" title="Sample selection of ALPC function" /></p>
<p><em>Once again the goal here is not to select a specific set of functions, but instead just <strong>making a selection</strong> based on something. It’s always a good idea to select things you know or that sound familiar and cycle down a learning path from there…</em></p>
<p>The upper list of the <em>no-component</em> ALPC functions does have a lot of function names containing the words “CompletionList”, which might or might not sound familiar to you. The bottom list of <em>Nt</em> ALPC functions on the other hand appears quite heterogeny and based on the <em>Nt</em> component naming convention I would assume that these functions are meant to be gateway functions from user-land to kernel-land. We’ve drilled down this far so let’s take one these functions and start the reversing job.<br />
There is no right and wrong in picking one, you can be lucky and pick a function that is meant to be used during the early stage of an ALPC setup, which has further hints on how to use ALPC, or one might unknowingly pick a function that is only meant for special ALPC scenarios<em>… the joy of undocumented stuff…</em><br />
At this point we can’t know which function is a good starting point, so let’s choose one that at least sounds like its meant to be used at the start of a process, like something with <em>Create</em> in its name:</p>
<p><img src="/public/img/2022-05-29-Debugging-and-Reversing-ALPC/ALPC_Function_Selected_NtAlpcCreatePort.png" alt="ALPC function selected NtAlpcCreatePort" title="Selected function: NTAlpcCreatePort" /></p>
<p><em>I obviously already know that this function is going to be useful, so forgive me the “let’s pick something randomly”-dance.</em></p>
<h2 id="from-user-to-kernel-land">From User to Kernel Land</h2>
<p>Let’s fire up <a href="https://ghidra-sre.org/">Ghidra</a> and have a look at the <code class="language-plaintext highlighter-rouge">NtAlpcCreatePort</code> function within <code class="language-plaintext highlighter-rouge">ntdll.dll</code>:</p>
<p><img src="/public/img/2022-05-29-Debugging-and-Reversing-ALPC/Ghidra_Ntdll_NtAlpcCreatePort.png" alt="Ghidra Ntdll NtAlpcCreatePort" title="NtAlpcCreatePort in Ghidra" /></p>
<p>Ok… this is not increadibly helpful… and also looks odd. A <a href="https://en.wikipedia.org/wiki/System_call">syscall</a> is made with no arguments and the function then returns the integer <em>0x79</em>…<br />
Double checking this decompiled code with the actual instructions displayed right next to the decompiled window, does show a different picture:</p>
<p><img src="/public/img/2022-05-29-Debugging-and-Reversing-ALPC/Ghidra_Ntdll_NtAlpcCreatePort_With_Assembly.png" alt="Ghidra Ntdll NtAlpcCreatePort with assembly" title="NtAlpcCreatePort in Ghidra with assembly code" /></p>
<p>The actual code instructions show that the integer value <em>0x79</em> is moved into <em>EAX</em> and then the <a href="https://en.wikipedia.org/wiki/System_call">syscall</a> is made. Quickly double checking this with <a href="https://hex-rays.com/ida-free/">IDA Free</a> to be sure:</p>
<p><img src="/public/img/2022-05-29-Debugging-and-Reversing-ALPC/IDA_Ntdll_NtAlpcCreatePort.png" alt="IDA Ntdll NtAlpcCreatePort" title="NtAlpcCreatePort in IDA Free" /></p>
<p>Yep, okay that makes more sense. First take away here is: <a href="https://ghidra-sre.org/">Ghidra</a> is a really great tool, the decompilation feature can be flaky (even for simple functions), but on the other hand: Automated decompilation is a massive feature that is handed out for free here, so no hard feelings about some errors and manual double checking effort.</p>
<p>We figured the <code class="language-plaintext highlighter-rouge">NtAlpcCreatePort</code> function within <code class="language-plaintext highlighter-rouge">ntdll.dll</code> is pretty much calling into kernel mode right away using the syscall number <code class="language-plaintext highlighter-rouge">0x79</code> (121 in decimal).<br />
From here we got three options to continue:</p>
<ul>
<li>Head to the kernel straight away and look for a function with a similar name and hope that we get the right one (ntdll and kernel function names are often very similar) - <em>This is the least reliable method</em>.</li>
<li>Lookup the syscall number (<em>0x79</em>) online to find the corresponding kernel function.</li>
<li>Manually step through the process of getting and resolving the syscall number on your host system - <em>This is the most reliable method</em>.</li>
</ul>
<p>Let’s skip lazy option 1 (least reliable) and check out options two and three.</p>
<p><strong>Lookup Syscall number online</strong><br />
One of the best (and most known) resources to lookup syscall numbers is <a href="https://j00ru.vexillium.org/syscalls/nt/64/">https://j00ru.vexillium.org/syscalls/nt/64/</a> (x86 syscalls can be found <a href="https://j00ru.vexillium.org/syscalls/nt/32/">here</a>).</p>
<p><img src="/public/img/2022-05-29-Debugging-and-Reversing-ALPC/Online_syscall_table.png" alt="Online syscall table" title="Syscall reference from https://j00ru.vexillium.org/syscalls/nt/64/" /></p>
<p>For my Windows 10 20H2 system this great online resource directly points me to a kernel function named “NtAlpcCreatePort”.</p>
<p><strong>Stepping through the syscall manually</strong><br />
<em>I’ve learned and applied the process from <a href="https://www.ired.team/miscellaneous-reversing-forensics/windows-kernel-internals/glimpse-into-ssdt-in-windows-x64-kernel">www.ired.team</a>, all credits and kudos go to ired.team !</em></p>
<p>We can use WinDbg to manually extract the corresponding kernel function from our debugged host systems. There are 6 steps involved here:</p>
<ol>
<li>Setting a breakpoint in ntdll at <code class="language-plaintext highlighter-rouge">ntdll!NtAlpcCreatePort</code> to jump into the function. This can be done through the following WinDbg command:<br />
<code class="language-plaintext highlighter-rouge">kd:> bp ntdll!NtAlpcCreatePort</code></li>
<li>Verify our breakpoint is set correctly, via: <code class="language-plaintext highlighter-rouge">kd:> bl</code>
<img src="/public/img/2022-05-29-Debugging-and-Reversing-ALPC/WinDbg_Resolve_Syscall_1.png" alt="WinDbg list breakpoint" title="List breakpoints in WinDbg" /></li>
<li>Let the debuggee run until this breakpoint in ntdll is hit: <code class="language-plaintext highlighter-rouge">kd:> g</code></li>
<li>Ensure we are at the correct location and have the syscall right ahead: <code class="language-plaintext highlighter-rouge">kd:> u .</code> (unassemble the next following instructions)
<img src="/public/img/2022-05-29-Debugging-and-Reversing-ALPC/WinDbg_Resolve_Syscall_2.png" alt="WinDbg show syscall" title="Disassembled syscall in WinDbg" /></li>
<li>Lookup the offset in the SSDT (System Service Descriptor Table) for the syscall number, <em>0x79</em>: <code class="language-plaintext highlighter-rouge">kd:> dd /c1 kiservicetable+4*0x79 L1</code></li>
<li>Checking the address of the syscall function using the SSDT offset: <code class="language-plaintext highlighter-rouge">kd:> u kiservicetable + (02b62100>>>4) L1</code></li>
</ol>
<p>All these steps can be found in the screenshot below:
<img src="/public/img/2022-05-29-Debugging-and-Reversing-ALPC/WinDbg_Resolve_Syscall_3.png" alt="WinDbg dispatched syscall" title="Dispatching a syscall in WinDbg" /></p>
<p>Using either of these three methods we would have come to the result that <code class="language-plaintext highlighter-rouge">ntdll!NtAlpcCreatePort</code> calls into the kernel at <code class="language-plaintext highlighter-rouge">nt!NtAlpcCreatePort</code></p>
<h2 id="hunting-an-alpc-object">Hunting An ALPC Object</h2>
<p>Now we’ve figured that we end up calling the kernel in <code class="language-plaintext highlighter-rouge">nt!NtAlpcCreatePort</code>, so let’s have a look at this.<br />
We can fire up <a href="https://hex-rays.com/ida-free/">IDA Free</a> (<a href="https://ghidra-sre.org/">Ghidra</a> would’ve been just as fine), open up <em>ntoskrnl.exe</em> from our system directory, e.g. <em>C:\Windows\System32\ntoskrnl.exe</em>, load Microsoft’s public symbols, and we should be able to find the function call <code class="language-plaintext highlighter-rouge">NtAlpcCreatePort</code>. From there on we can browse through the functions that are called to get a first idea of what’s going on under the hood for this call.</p>
<p><img src="/public/img/2022-05-29-Debugging-and-Reversing-ALPC/IDA_NtlpcCreatePort.png" alt="IDA NtlpcCreatePort" title="NtAlpcCreatePort in IDA Free" /></p>
<p>Following the first few function calls will route us to a call to <code class="language-plaintext highlighter-rouge">ObCreateObjectEx</code>, which is an ObjectManager (Ob) function call to create a kernel object. That sounds like our ALPC object is created here and IDA also tells us what type of object that is, two lines above the marked call in the window on the right, a <code class="language-plaintext highlighter-rouge">AlpcPortObjectType</code>. At this point I’d like to try to get a hold of such an object to get a better understanding and insight of what this actually is. As the function <code class="language-plaintext highlighter-rouge">ObCreateObjectEx</code> will create the object the plan here is to switch back to WinDbg and set a breakpoint right after this call to find and inspect the created object.</p>
<p><img src="/public/img/2022-05-29-Debugging-and-Reversing-ALPC/WinDbg_Breakpoint_NtAlpcpCreatePort.png" alt="WinDbg breakpoint NtAlpcpCreatePort" title="NtAlpcpCreatePort breakpoint in WinDbg" /></p>
<p>After placing this breakpoint we hit <code class="language-plaintext highlighter-rouge">g</code> to let WinDbg run and once it hits we check if we can find the created object being referenced somewhere. The reliable method for this is to follow the object creation process in <code class="language-plaintext highlighter-rouge">ObCreateObjectEx</code> and track where the object is stored once the function finishes (the less reliable option is to check the common registers and the stack after the function finishes).<br />
In this case we can find the created ALPC object in the RCX register once we hit our breakpoint.</p>
<p><img src="/public/img/2022-05-29-Debugging-and-Reversing-ALPC/WinDbg_Breakpoint_NtAlpcpCreatePort_2.png" alt="WinDbg ALPC port object" title="ALPC port object in WinDbg" /></p>
<p>Sweet we found a newly created ALPC port object. At this point the <code class="language-plaintext highlighter-rouge">!object</code> command can tell us the type of the object, the location of its header and its name, but it can’t add additional detail for this object, because it does not now its internal structure. We do not know either, but we could check if there is a matching public structure inside the kernel that we can resolve. We’ll try that with <code class="language-plaintext highlighter-rouge">kd:> dt nt!*Alpc*Port</code>…</p>
<p><img src="/public/img/2022-05-29-Debugging-and-Reversing-ALPC/WinDbg_resolve_AlpcPortStructure.png" alt="WinDbg resolved AlpcPortStructure" title="Resolved _ALPC_PORT structure in WinDbg" /></p>
<p>We once again used wildcards combined with the information we obtained so far, which are: We’re looking for a structure inside the kernel module (<em>nt</em>) and we’re looking for a structure that matches an object that we knew is of type <em>AlpcPortObjectType</em>. The naming convention in Windows often names structures with a leading underscore and all capital letters. The first hit <code class="language-plaintext highlighter-rouge">ntkrnlmp!_ALPC_PORT</code> looks like a promising match, so let’s stuff our captured ALPC port object in this structure:</p>
<p><img src="/public/img/2022-05-29-Debugging-and-Reversing-ALPC/WinDbg_Filled_ALPCPort_Structure1.png" alt="WinDbg filled ALPCPort structure" title="Applied _ALPC_PORT structure in WinDbg" /></p>
<p>That does indeed look like a match, however some attributes, that one would expect to be set, are empty, for example the “OwnerProcess” attribute. Before we throw our match in the bin, let’s remember we’re still in the breakpoint right after <code class="language-plaintext highlighter-rouge">ObCreateObjectEx</code>, so the object has just been created. Walking back through functions we’ve traversed in IDA, we can find that there are a couple more functions to be called within the <code class="language-plaintext highlighter-rouge">AlpcpCreateConnectionPort</code> function, such as <code class="language-plaintext highlighter-rouge">AlpcpInitializePort</code>, <code class="language-plaintext highlighter-rouge">AlpcpValidateAndSetPortAttributes</code> and others. Sounds like there is more to come that we want to catch.</p>
<p>Right now, we’re in some process that created an ALPC port (so far we didn’t even bother to check which process that is) and we want to jump to a code location after all the initialization functions are completed and check what our ALPC port structure looks like then, so here’s a rundown of what we want we want to do:</p>
<ol>
<li>We want to note down the address of our ALPC object for later reference.</li>
<li>We want to find the end of the <code class="language-plaintext highlighter-rouge">AlpcpCreateConnectionPort</code> function.</li>
<li>We want to jump to this location within the same process that we currently are in,</li>
<li>We want to load our noted ALPC object into the <code class="language-plaintext highlighter-rouge">ntkrnlmp!_ALPC_PORT</code> structure to see what it looks like.</li>
</ol>
<p>And here’s how to do that…</p>
<ol>
<li>Noting down the ALPC object address… Done: <code class="language-plaintext highlighter-rouge">ffffac0e27ab96e0</code>
<img src="/public/img/2022-05-29-Debugging-and-Reversing-ALPC/WinDbg_ALPC_Port_Object.png" alt="WinDbg ALPC Port Object" title="Noting down the ALPC Port object reference" /></li>
<li>Finding the end of <code class="language-plaintext highlighter-rouge">AlpcpCreateConnectionPort</code>… Done jumping to <code class="language-plaintext highlighter-rouge">0xfffff803733823c9</code>
<img src="/public/img/2022-05-29-Debugging-and-Reversing-ALPC/WinDdbg_FindFunctionEnd.png" alt="WinDdbg find function end" title="Finding the end of the AlpcpCreateConnectionPort function" /></li>
<li>Jump to this address within the same process can be done using this command <code class="language-plaintext highlighter-rouge">kd:> bp /p @$proc fffff803733823c9</code><br />
<em>Note: I’m also checking in which process I am before and after the call just to be on the safe side</em>
<img src="/public/img/2022-05-29-Debugging-and-Reversing-ALPC/WinDbg_ALPCPortCreation_Jump.png" alt="WinDbg ALPC Port creation jump" title="Jumping to the located address" /></li>
<li>Check ALPC Objet structure again…
<img src="/public/img/2022-05-29-Debugging-and-Reversing-ALPC/WinDbg_Filled_ALPCPort_Structure2.png" alt="WinDbg filled ALPCPort structure 2" title="Re-applied _ALPC_PORT structure in WinDbg" /></li>
</ol>
<p>That looks more complete and we could walk through an all setup ALPC object from here as easy as using the links provided by WinDbg to inspect what other structures and references are linked to this object.<br />
Just for the sake of providing an example and to double confirm that this ALPC Port object is indeed owned by the <em>svchost.exe</em> process that we identified above, we can inspect the <em>_EPROCESS</em> structure that is shown at <code class="language-plaintext highlighter-rouge">ntkrnlmp!_ALPC_PORT + 0x18</code>:</p>
<p><img src="/public/img/2022-05-29-Debugging-and-Reversing-ALPC/WinDbg_EPROCESS_Of_ALPC_Object.png" alt="WinDbg EPROCESS of ALPC object" title="_EPROCESS structure of the owning process in WinDbg" /></p>
<p>We find the <em>ImageFileName</em> of the owning process of the ALPC object that we’ve caught to be “svchost.exe”, which matches with the process we’re currently in.</p>
<p>At this point we’ve found an all setup ALPC port object that we could further dissect in WinDbg to explore other attributes of this kernel object. I’m not going any deeper here at this point, but if you got hooked on digging deeper feel free to continue the exploration tour.<br />
If you’re following this path, you might want to explore the ALPC port attributes assigned to the port object you found, which are tracked in the <code class="language-plaintext highlighter-rouge">nt!_ALPC_PORT_ATTRIBUTES</code> structure at <code class="language-plaintext highlighter-rouge">nt!_ALPC_PORT + 0x100</code> to check the <a href="https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-security_quality_of_service">Quality of Service</a> (QOS) attribute assigned to this object (<code class="language-plaintext highlighter-rouge">nt!_ALPC_PORT + 0x100 + 0x04</code>).</p>
<p>If you found an ALPC port object with an (QOS) <a href="https://docs.microsoft.com/en-us/windows/win32/api/winnt/ne-winnt-security_impersonation_level">impersonation level</a> above <em>SecurityIdentification</em>, you might have found an interesting target for an <a href="/2022/05/24/Offensive-Windows-IPC-3-ALPC.html#impersonation-and-non-impersonation">impersonation attack</a>, detailed in my previous post <a href="/2022/05/24/Offensive-Windows-IPC-3-ALPC.html">Offensive Windows IPC Internals 3: ALPC</a>.</p>
<p><img src="/public/img/2022-05-29-Debugging-and-Reversing-ALPC/WinDbg_QOS_ImpersonationLevel.png" alt="WinDbg QOS impersonation level" title="_SECURITY_QUALITY_OF_SERVICE structure of the identified ALPC port object in WinDbg" /></p>
<p>In this case, it’s only <em>SecurityAnonymous</em>, well…</p>
<p><img src="/public/img/2022-05-29-Debugging-and-Reversing-ALPC/Meme_AtLeastYouTried.jpg" alt="At Least you tried" /></p>
<p>By now you should be all set up to explore and dig into ALPC. The first steps are obviously going to be slow and you (and I) will take a few wrong turns, but that is part of everyone’s learning experience.</p>
<p>If I could add a last note to aid in getting on a joyful ride it’s this: I personally enjoy reading good old, paperback books, to learn, dig deeper and to improve my skillset with Windows internals. If you are of similar kind, you might as well enjoy these book references (if you not already have them on your desk):</p>
<ul>
<li><a href="https://www.microsoftpressstore.com/store/windows-internals-part-1-system-architecture-processes-9780735684188">Windows Internals Part 1</a></li>
<li><a href="https://www.microsoftpressstore.com/store/windows-internals-part-2-9780135462409">Windows Internals Part 2</a></li>
<li><a href="https://www.microsoftpressstore.com/store/inside-windows-debugging-9780735662780">Inside Windows Debugging</a></li>
<li><a href="https://leanpub.com/windowskernelprogrammingsecondedition">Windows Kernel Programming</a><br />
<em>There already is a published 1st edition of this, but if you want the latest and greates you might want to wait for <a href="twitter.com/zodiacon">@zodiacon’s</a> new release.</em></li>
</ul>
<p><br />
<em>… Enjoy your ride ;) …</em></p>Contents:Offensive Windows IPC Internals 3: ALPC2022-05-24T08:00:00+00:002022-05-24T08:00:00+00:00https://csandker.io//2022/05/24/Offensive-Windows-IPC-3-ALPC<h2 class="no_toc toc-header" id="contents">Contents:</h2>
<ul id="markdown-toc">
<li><a href="#introduction" id="markdown-toc-introduction">Introduction</a></li>
<li><a href="#alpc-internals" id="markdown-toc-alpc-internals">ALPC Internals</a> <ul>
<li><a href="#the-basics" id="markdown-toc-the-basics">The Basics</a></li>
<li><a href="#alpc-message-flow" id="markdown-toc-alpc-message-flow">ALPC Message Flow</a></li>
<li><a href="#alpc-messaging-details" id="markdown-toc-alpc-messaging-details">ALPC Messaging Details</a></li>
<li><a href="#alpc-message-attributes" id="markdown-toc-alpc-message-attributes">ALPC Message Attributes</a></li>
</ul>
</li>
<li><a href="#putting-the-pieces-together-a-sample-application" id="markdown-toc-putting-the-pieces-together-a-sample-application">Putting the pieces together: A Sample Application</a></li>
<li><a href="#attack-surface" id="markdown-toc-attack-surface">Attack Surface</a> <ul>
<li><a href="#identify-targets" id="markdown-toc-identify-targets">Identify Targets</a></li>
<li><a href="#impersonation-and-non-impersonation" id="markdown-toc-impersonation-and-non-impersonation">Impersonation and Non-Impersonation</a></li>
<li><a href="#unfreed-message-objects" id="markdown-toc-unfreed-message-objects">Unfreed Message Objects</a></li>
</ul>
</li>
<li><a href="#conclusion" id="markdown-toc-conclusion">Conclusion</a></li>
<li><a href="#appendix-a-the-use-of-connection-and-communication-ports" id="markdown-toc-appendix-a-the-use-of-connection-and-communication-ports">Appendix A: The use of connection and communication ports</a></li>
<li><a href="#references" id="markdown-toc-references">References</a></li>
</ul>
<h2 id="introduction">Introduction</h2>
<p>After talking about two inter-process communication (<strong>IPC</strong>) protocols that can be uses remotely as well as locally, namely <a href="/2021/01/10/Offensive-Windows-IPC-1-NamedPipes.html">Named Pipes</a> and <a href="/2021/02/21/Offensive-Windows-IPC-2-RPC.html">RPC</a>, with ALPC we’re now looking at a technology that can only be used locally. While RPC stands for <strong>R</strong>emote <strong>P</strong>rocedure <strong>C</strong>all, ALPC reads out to <strong>A</strong>dvanced <strong>L</strong>ocal <strong>P</strong>rocedure <strong>C</strong>all, sometimes also referenced as <strong>A</strong>synchronous <strong>L</strong>ocal <strong>P</strong>rocedure <strong>C</strong>all. Especially the later reference (asynchronous) is a reference to the days of Windows Vista when ALPC was introduced to replace LPC (Local Procedure Call), which is the predecessor IPC mechanism used until the rise of Windows Vista.<br /></p>
<p><strong>A quick word on LPC</strong><br />
The local procedure call mechanism was introduced with the original Windows NT kernel in 1993-94 as a <strong>synchronous</strong> inter-process communication facility. Its synchronous nature meant that clients/servers had to wait for a message to dispatched and acted upon before execution could continue. This was one of the main flaws that ALPC was designed to replace and the reason why ALPC is referred to by some as <strong>asynchronous</strong> LPC.<br />
ALPC was brought to light with Windows Vista and at least from Windows 7 onward LPC was completely removed from the NT kernel. To not break legacy applications and allow for backwards compatibility, which Microsoft is (in)famously known for, the function used to create an LPC port was kept, but the function call was redirected to not create an LPC, but an ALPC port.</p>
<p><img src="/public/img/2022-05-24-Offensive-Windows-IPC-3-ALPC/LPC_CreatePort_Win7.png" alt="LPC CreatePort in Windows 7" title="CreatePort API Call in Windows 7" /></p>
<p>As LPC is effectively gone since Windows 7, this post will only focus on ALPC, so let’s get back to it.<br />
<em>But, if you’re - like me - enjoy reading old(er) documentations of how things started out and how things used to for work, here’s an article going in some detail about how LPC used to work in Windows NT 3.5: <a href="http://web.archive.org/web/20090220111555/http://www.windowsitlibrary.com/Content/356/08/1.html">http://web.archive.org/web/20090220111555/http://www.windowsitlibrary.com/Content/356/08/1.html</a></em></p>
<p><strong>Back to ALPC</strong><br />
ALPC is a fast, very powerful and within the Windows OS (internally) very extensively used inter-process communication facility, but it’s not intended to be used by developers, because to Microsoft ALPC is an internal IPC facility, which means that ALPC is <strong>undocumented</strong> and only used as the underlying transportation technology for other, documented and intended-for-developer-usage message transportation protocols, for example RPC.<br />
The fact that ALPC is undocumented (by Microsoft), does however not mean that ALPC is a total blackbox as smart folks like <a href="https://twitter.com/aionescu">Alex Ionescu</a> have reverse engineered how it works and what components it has. But what it <em>does mean</em> is that you shouldn’t rely on any ALPC behavior for any long-term production usage and even more you really shouldn’t use ALPC directly to build software as there are a lot of non-obvious pitfalls that could cause security or stability problems.<br />
If you feel like you could hear another voice on ALPC after reading this post, I highly recommend listening to <a href="(https://twitter.com/aionescu)">Alex’s</a> <a href="https://www.youtube.com/watch?v=UNpL5csYC1E">ALPC talk from SyScan’14</a> and especially keep an ear open when Alex talks about what steps are necessary to release a mapped view (and that’s only addressing views) from your ALPC server, which gets you at around <a href="https://www.youtube.com/watch?v=UNpL5csYC1E#t=33m18s">minute 33 of the talk</a>.</p>
<p>So what I’m saying here is:</p>
<blockquote>
<p><strong>ALPC is a very interesting target, but not intended for (non-Microsoft) usage in production development. Also you shouldn’t rely on all the information in this post being or continue to be 100% accurate as ALPC is undocumented.</strong></p>
</blockquote>
<h2 id="alpc-internals">ALPC Internals</h2>
<p>Alright let’s get into some ALPC internals to understand how ALPC works, what moving parts are involved in the communications and how the messages look like to finally get an idea of why ALPC might be an interesting target from an offensive security standpoint.</p>
<h3 id="the-basics">The Basics</h3>
<p>To get off from the ground it should be noted that the primary components of ALPC communications are ALPC port objects. An ALPC port object is a kernel object and its use is similar to the use of a network socket, where a server opens a socket that a client can connect to in order to exchange messages.<br />
If you fire up <a href="https://docs.microsoft.com/en-us/sysinternals/downloads/winobj">WinObj</a> from the <a href="https://docs.microsoft.com/en-us/sysinternals/">Sysinternals Suite</a>, you’ll find that there are many ALPC ports running on every Windows OS, a few can be found under the root path as shown below:</p>
<p><img src="/public/img/2022-05-24-Offensive-Windows-IPC-3-ALPC/WinObj_ALPC-Ports_Root.png" alt="ALPC Ports under the root path in WinObj" title="ALPC Ports under root path" /></p>
<p>… but the majority of ALPCs port are housed under the ‘RPC Control’ path (remember that RPC uses ALPC under the hood):</p>
<p><img src="/public/img/2022-05-24-Offensive-Windows-IPC-3-ALPC/WinObj_ALPC-Ports_RPC-Control.png" alt="WinObj_ALPC-Ports_RPC-Control.png" title="ALPC under \\RPC Control" /></p>
<p>To get started with an ALPC communication, a server opens up an ALPC port that clients can connect to, which is referred to as the <strong>ALPC Connection Port</strong>, however, that’s not the only ALPC port that is created during an ALPC communication flow (as you’ll see in the next chapter). Another two ALPC ports are created for the client and for the server to pass messages to.<br />
So, the first thing to make a mental note of is:</p>
<ul>
<li>There are 3 ALPC ports in total (2 on the server side and 1 on the client side) involved in an ALPC communication.</li>
<li>The ports you saw in the <a href="https://docs.microsoft.com/en-us/sysinternals/downloads/winobj">WinObj</a> screenshot above are <strong>ALPC Connection Ports</strong>, which are the ones a client can connect to.</li>
</ul>
<p>Although there are 3 ALPC ports used in total in an ALPC communication and they all are referred to by different names (such as “ALPC Connection Ports”), there is only a single ALPC port kernel object, which all three ports, used in an ALPC communication, instantiate. The skeleton of this ALPC kernel object looks like this:</p>
<p><img src="/public/img/2022-05-24-Offensive-Windows-IPC-3-ALPC/ALPC_Port_KernelStructure.png" alt="ALPC Kernel Object" title="_ALPC_PORT kernel structure" /></p>
<p>As it can be seen above the ALPC kernel object is a quite complex kernel object, referencing various other object types. This makes it an interesting research target, but also leaves some good margin for errors and/or missed attack paths.</p>
<h3 id="alpc-message-flow">ALPC Message Flow</h3>
<p>To dig deeper into ALPC we’ll have a look into the ALPC message flow to understand how messages are sent and how these could look like.
First of all we’ve already learned that 3 ALPC port objects are involved in an ALPC communication scenario, with the first one being the <strong>ALPC connection port</strong> that is created by a server process and that clients can connect to (similar to a network socket). Once a client connects to a server’s ALPC connection port, two new ports are created by the kernel called <strong>ALPC server communication port</strong> and <strong>ALPC client communication port</strong>.</p>
<p><img src="/public/img/2022-05-24-Offensive-Windows-IPC-3-ALPC/ALPC_PortObjects_Diagam1.png" alt="ALPC Port Object Relationship" title="ALPC Port Object Relationship" /></p>
<p>Once the server and client communication ports are established both parties can send messages to each other using the single function <code class="language-plaintext highlighter-rouge">NtAlpcSendWaitReceivePort</code> exposed by <em>ntdll.dll</em>.<br />
The name of this function sounds like three things at once - Send, Wait and Receive - and that’s exactly what it is. Server and client use this single function to wait for messages, send messages and receive messages on their ALPC port. This sounds unnecessary complex and I can’t tell you for sure why it was build this way, but here’s my guess on it: Remember that ALPC was created as a fast and internal-only communication facility and the communication channel was build around a single kernel object (the ALPC port). Using this 3-way function allows to do multiple operations, e.g. sending and receiving a message, in a single call and thus saves time and reduces user-kernel-land switches. Additionally, this function acts as a single gate into the message exchange process and therefore allows for easier code change and optimizations (ALPC communication is used in a lot of different OS components ranging from kernel drivers to user GUI applications developed by different internal teams). Lastly ALPC is intended as an internal-only IPC mechanism so Microsoft does not need to design it primarily user or 3rd party developer friendly.<br />
Within this single function you also specify what kind of message you want to send (there are different kinds with different implications, <em>we’ll get to that later on</em>) and what other attributes you want to send along with your message (again we’ll get to the things that you can send along with a message later on in chapter <a href="#alpc-message-attributes">ALPC Message Attributes</a>).</p>
<p>So far this sounds pretty straight forward: A server opens a port, a client connects to it, both receive a handle to a communication port and send along messages through the single function <code class="language-plaintext highlighter-rouge">NtAlpcSendWaitReceivePort</code>… easy.<br />
We’ll on a high level it is that easy, but you surely came here for the details and the title of the post said “internals” so let’s buckle up for a closer look:</p>
<ol>
<li><strong>A server</strong> process calls <a href="https://github.com/csandker/InterProcessCommunication-Samples/blob/master/ALPC/CPP-ALPC-Basic-Client-Server/CPP-Util/ALPC.h#L300-L305">NtAlpcCreatePort</a> with a chosen ALPC port name, e.g. ‘<em>CSALPCPort</em>’, and optionally with a <a href="https://docs.microsoft.com/en-us/windows/win32/secauthz/security-descriptors">SecurityDescriptor</a> to specify who can connect to it.<br />
The kernel creates an ALPC port object and returns a handle this object to the server, this port is referred to as the <strong>ALPC Connection Port</strong></li>
<li><strong>The server</strong> calls <a href="https://github.com/csandker/InterProcessCommunication-Samples/blob/master/ALPC/CPP-ALPC-Basic-Client-Server/CPP-Util/ALPC.h#L322-L332">NtAlpcSendWaitReceivePort</a>, passing in the handle to its previously created connection port, to wait for client connections</li>
<li><strong>A client</strong> can then call <a href="https://github.com/csandker/InterProcessCommunication-Samples/blob/master/ALPC/CPP-ALPC-Basic-Client-Server/CPP-Util/ALPC.h#L307-L320">NtAlpcConnectPort</a> with:
<ul>
<li>The name of the server’s ALPC port (<em>CSALPCPort</em>)</li>
<li><em>(OPTIONALLY)</em> a message for the server (e.g. to send a magic keyword or whatever)</li>
<li><em>(OPTIONALLY)</em> the SID of server to ensure the client connects to the intended server</li>
<li><em>(OPTIONALLY)</em> message attributes to send along with the client’s connection request<br />
<em>(Message attributes will be detailed in chapter <a href="#alpc-message-attributes">ALPC Message Attributes</a>)</em></li>
</ul>
</li>
<li>This connection request is then passed to <strong>the server</strong>, which calls <a href="https://github.com/csandker/InterProcessCommunication-Samples/blob/master/ALPC/CPP-ALPC-Basic-Client-Server/CPP-Util/ALPC.h#L334-L345">NtAlpcAcceptConnectPort</a> to accept or reject the client’s connection request.<br /> <em>(Yes, although the function is named NtAlpcAccept… this function can also be used to reject client connections. This functions last parameter is a boolean value that specifies if connection are accepted (if set to <code class="language-plaintext highlighter-rouge">true</code>) or rejected (if set to <code class="language-plaintext highlighter-rouge">false</code>).</em><br />
The server can also:
<ul>
<li><em>(OPTIONALLY)</em> return a message to the client with the acceptance or denial of the connection request and/or…</li>
<li><em>(OPTIONALLY)</em> add message attributes to that message and/or ..</li>
<li><em>(OPTIONALLY)</em> allocate a custom structure, for example a unique ID, that is attached to the server’s communication port in order to identify the client<br />
<em>— If the server accepts the connection request, the server and the client each receive a handle to a communication port —</em></li>
</ul>
</li>
<li><strong>Client</strong> and <strong>server</strong> can now send and receive messages to/from each other via <a href="https://github.com/csandker/InterProcessCommunication-Samples/blob/master/ALPC/CPP-ALPC-Basic-Client-Server/CPP-Util/ALPC.h#L322-L332">NtAlpcSendWaitReceivePort</a>, where:
<ul>
<li>The <strong>client</strong> listens for and sends new messages to its <em>communication</em> port</li>
<li>The <strong>server</strong> listens for and sends new messages to its <em>connection</em> port</li>
<li>Both the <strong>client</strong> and the <strong>server</strong> can specify which <em>message attributes</em> (we’ll get to tht in a bit) they want to receive when listening for new messages</li>
</ul>
</li>
</ol>
<p><em>… wait a minute… Why is the server sending/receiving data on the <u>connection port</u> instead of its <u>communication port</u>, since it has a dedicated communication port?… This was one of the many things that puzzled me on ALPC and instead of doing all the heavy lifting reversing work to figure that out myself, I cheated and reached out to <a href="https://twitter.com/aionescu">Alex Ionescu</a> and simply asked the expert. I put the answer in <a href="#appendix-a-the-use-of-connection-and-communication-ports">Appendix A</a> at the end of this post, as I don’t want to drive too far away from the message flow at this point… sorry for the cliff hanger …</em></p>
<p>Anyhow, looking back at the message flow from above, we can figure that client and server are using various functions calls to create ALPC ports and then sending and receiving messages through the single function <code class="language-plaintext highlighter-rouge">NtAlpcSendWaitReceivePort</code>. While this contains a fair amount of information about the message flow it’s important to always be aware that server and client do not have a direct peer-to-peer connection, but instead route all messages through the kernel, which is responsible for placing messages on message queues, notifying each party of received messages and other things like validating messages and message attributes. To put that in perspective I’ve added <em>some</em> kernel calls into this picture:</p>
<p><img src="/public/img/2022-05-24-Offensive-Windows-IPC-3-ALPC/ALPC_Message_Flow.svg" alt="ALPC Message Flow" title="ALPC Message Flow" /></p>
<p>I have to admit on a first glance this is diagram is not super intuitive, but I’ll promise things will get clearer on the way, bear with me.<br />
To get a more complete picture of what ALPC looks like under the hood, we need to dive a little deeper into the implementation bits of ALPC messages, which I’ll cover in the following section.</p>
<h3 id="alpc-messaging-details">ALPC Messaging Details</h3>
<p>Okay so first of all, let’s clarify the structure of an ALPC message. An ALPC message always consist of a, so called, <em>PORT_HEADER</em> or <em>PORT_MESSAGE</em>, followed by the actual message that you want to send, e.g. some text, binary content, or anything else.</p>
<p><img src="/public/img/2022-05-24-Offensive-Windows-IPC-3-ALPC/ALPC_Kernel_PortMessage_Structure.png" alt="ALPC_Kernel_PortMessage_Structure.png" title="_PORT_MESSAGE Kernel Structure" /></p>
<p>In plain old C++ we can define an ALPC message with the following two structs:</p>
<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
</pre></td><td class="code"><pre><span class="k">typedef</span> <span class="k">struct</span> <span class="nc">_ALPC_MESSAGE</span> <span class="p">{</span>
<span class="n">PORT_MESSAGE</span> <span class="n">PortHeader</span><span class="p">;</span>
<span class="n">BYTE</span> <span class="n">PortMessage</span><span class="p">[</span><span class="mi">100</span><span class="p">];</span> <span class="c1">// using a byte array of size 100 to store my actual message</span>
<span class="p">}</span> <span class="n">ALPC_MESSAGE</span><span class="p">,</span> <span class="o">*</span> <span class="n">PALPC_MESSAGE</span><span class="p">;</span>
<span class="k">typedef</span> <span class="k">struct</span> <span class="nc">_PORT_MESSAGE</span>
<span class="p">{</span>
<span class="k">union</span> <span class="p">{</span>
<span class="k">struct</span> <span class="p">{</span>
<span class="n">USHORT</span> <span class="n">DataLength</span><span class="p">;</span>
<span class="n">USHORT</span> <span class="n">TotalLength</span><span class="p">;</span>
<span class="p">}</span> <span class="n">s1</span><span class="p">;</span>
<span class="n">ULONG</span> <span class="n">Length</span><span class="p">;</span>
<span class="p">}</span> <span class="n">u1</span><span class="p">;</span>
<span class="k">union</span> <span class="p">{</span>
<span class="k">struct</span> <span class="p">{</span>
<span class="n">USHORT</span> <span class="n">Type</span><span class="p">;</span>
<span class="n">USHORT</span> <span class="n">DataInfoOffset</span><span class="p">;</span>
<span class="p">}</span> <span class="n">s2</span><span class="p">;</span>
<span class="n">ULONG</span> <span class="n">ZeroInit</span><span class="p">;</span>
<span class="p">}</span> <span class="n">u2</span><span class="p">;</span>
<span class="k">union</span> <span class="p">{</span>
<span class="n">CLIENT_ID</span> <span class="n">ClientId</span><span class="p">;</span>
<span class="kt">double</span> <span class="n">DoNotUseThisField</span><span class="p">;</span>
<span class="p">};</span>
<span class="n">ULONG</span> <span class="n">MessageId</span><span class="p">;</span>
<span class="k">union</span> <span class="p">{</span>
<span class="n">SIZE_T</span> <span class="n">ClientViewSize</span><span class="p">;</span>
<span class="n">ULONG</span> <span class="n">CallbackId</span><span class="p">;</span>
<span class="p">};</span>
<span class="p">}</span> <span class="n">PORT_MESSAGE</span><span class="p">,</span> <span class="o">*</span> <span class="n">PPORT_MESSAGE</span><span class="p">;</span>
</pre></td></tr></tbody></table></code></pre></figure>
<p>In order to send a message all we have to do is the following:</p>
<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
</pre></td><td class="code"><pre><span class="c1">// specify the message struct and fill it with all 0's to get a clear start</span>
<span class="n">ALPC_MESSAGE</span> <span class="n">pmSend</span><span class="p">,</span> <span class="n">pmReceived</span><span class="p">;</span>
<span class="n">RtlSecureZeroMemory</span><span class="p">(</span><span class="o">&</span><span class="n">pmSend</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">pmSend</span><span class="p">));</span>
<span class="n">RtlSecureZeroMemory</span><span class="p">(</span><span class="o">&</span><span class="n">pmReceived</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">pmReceived</span><span class="p">));</span>
<span class="c1">// getting a pointer to my payload (message) byte array</span>
<span class="n">LPVOID</span> <span class="n">lpPortMessage</span> <span class="o">=</span> <span class="n">pmSend</span><span class="o">-></span><span class="n">PortMessage</span><span class="p">;</span>
<span class="n">LPCSTR</span> <span class="n">lpMessage</span> <span class="o">=</span> <span class="s">"Hello World!"</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">lMsgLen</span> <span class="o">=</span> <span class="n">strlen</span><span class="p">(</span><span class="n">lpMessage</span><span class="p">);</span>
<span class="c1">// copying my message into the message byte array</span>
<span class="n">memmove</span><span class="p">(</span><span class="n">lpPortMessage</span><span class="p">,</span> <span class="n">messageContent</span><span class="p">,</span> <span class="n">lMsgLen</span><span class="p">);</span>
<span class="c1">// specify the length of the message</span>
<span class="n">pMessage</span><span class="o">-></span><span class="n">PortHeader</span><span class="p">.</span><span class="n">u1</span><span class="p">.</span><span class="n">s1</span><span class="p">.</span><span class="n">DataLength</span> <span class="o">=</span> <span class="n">lMsgLen</span><span class="p">;</span>
<span class="c1">// specify the total length of the ALPC message</span>
<span class="n">pMessage</span><span class="o">-></span><span class="n">PortHeader</span><span class="p">.</span><span class="n">u1</span><span class="p">.</span><span class="n">s1</span><span class="p">.</span><span class="n">TotalLength</span> <span class="o">=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">PORT_MESSAGE</span><span class="p">)</span> <span class="o">+</span> <span class="n">lMsgLen</span><span class="p">;</span>
<span class="c1">// Send the ALPC message</span>
<span class="n">NTSTATUS</span> <span class="n">lSuccess</span> <span class="o">=</span> <span class="n">NtAlpcSendWaitReceivePort</span><span class="p">(</span>
<span class="n">hCommunicationPort</span><span class="p">,</span> <span class="c1">// the client's communication port handle</span>
<span class="n">ALPC_MSGFLG_SYNC_REQUEST</span><span class="p">,</span> <span class="c1">// message flags: synchronous message (send & receive message) </span>
<span class="p">(</span><span class="n">PPORT_MESSAGE</span><span class="p">)</span><span class="o">&</span><span class="n">pmSend</span><span class="p">,</span> <span class="c1">// our ALPC message</span>
<span class="nb">NULL</span><span class="p">,</span> <span class="c1">// sending message attributes: we don't need that in the first step</span>
<span class="p">(</span><span class="n">PPORT_MESSAGE</span><span class="p">)</span><span class="o">&</span><span class="n">pmReceived</span><span class="p">,</span> <span class="c1">// ALPC message buffer to receive a message</span>
<span class="o">&</span><span class="n">ulReceivedSize</span><span class="p">,</span> <span class="c1">// SIZE_T ulReceivedSize; Size of the received message</span>
<span class="nb">NULL</span><span class="p">,</span> <span class="c1">// receiving message attributes: we don't need that in the first step</span>
<span class="mi">0</span> <span class="c1">// timeout parameter, we don't want to timeout</span>
<span class="p">);</span>
</pre></td></tr></tbody></table></code></pre></figure>
<p>This code snippet will send an ALPC message with a body of “Hello World!” to a server that we’ve connect to. We specified the message to be synchronous message with the <code class="language-plaintext highlighter-rouge">ALPC_MSGFLG_SYNC_REQUEST</code> flag, which means that this call will wait (block) until a message is received on the client’s communication port.<br />
Of course we do not have to wait until a new message comes in, but use the time until then for other tasks (remember ALPC was build to be asynchronous, fast and efficient). To facilitate that ALPC provides three different message types:</p>
<ul>
<li><strong>Synchronous request</strong>: As mentioned above synchronous messages block until a new message comes in (as a logical result of that one has to specify a receiving ALPC message buffer when calling <code class="language-plaintext highlighter-rouge">NtAlpcSendWaitReceivePort</code> with a synchronous messages)</li>
<li><strong>Asynchronous request</strong>: Asynchronous messages send out your message, but not wait for or act on any received messages.</li>
<li><strong>Datagram requests</strong>: Datagram request are like UDP packets, they don’t expect a reply and therefore the kernel does not block on waiting for a received message when sending a datagram request.</li>
</ul>
<p>So basically you can choose to send a message that expects a reply or one that does not and when you chose the former you can furthermore chose to wait until the reply comes in or don’t wait and do something else with your valuable CPU time in the meantime. That leaves you with the question of how to receive a reply in case you chose this last option and not wait (asynchronous request) within the <code class="language-plaintext highlighter-rouge">NtAlpcSendWaitReceivePort</code> function call?<br />
Once again you have 3 options:</p>
<ul>
<li>You could use an ALPC completion list, in which case the kernel does <em>not</em> inform you (as the receiver) that new data has been received, but instead simply copies the data into your process memory. It’s up to you (as the receiver) to get aware of this new data being present. This could for example achieved by using a notification event that is shared between you and the ALPC server¹. Once the server signals the event, you know new data has arrived.<br />
<small>¹Taken from <a href="https://www.microsoftpressstore.com/store/windows-internals-part-2-9780135462409">Windows Internals, Part 2, 7th Edition</a>.</small></li>
<li>You could use an I/O completion port, which is a <a href="https://docs.microsoft.com/en-us/windows/win32/fileio/i-o-completion-ports">documented</a> synchronization facility.</li>
<li>You can receive a kernel callback to get replies - but that is only allowed if your process lives in kernel land.</li>
</ul>
<p>As you have the option to not receive messages directly it is not unlikely that more than one message comes in and waits for being fetched. To handle multiple messages in different states ALPC uses queues to handle and manage high volumes of messages piling up for a server. There are five different queues for messages and to distinguish them I’ll quote directly from chapter 8 of <a href="https://www.microsoftpressstore.com/store/windows-internals-part-2-9780135462409">Windows Internals, Part 2, 7th Edition</a> (as there is no better way to put this with these few words):</p>
<blockquote>
<ul>
<li><strong>Main queue</strong>: A message has been sent, and the client is processing it.</li>
<li><strong>Pending queue</strong>: A message has been sent and the caller is waiting for a reply, but the reply has not yet been sent.</li>
<li><strong>Large message queue</strong>: A message has been sent, but the caller’s buffer was to small to receive it. The caller gets another chance to allocate a larger buffer and request the message payload again.</li>
<li><strong>Canceled queue</strong>: A message that was sent to the port but has since then been canceled.</li>
<li><strong>Direct queue</strong>: A message that was sent with a direct event attached.</li>
</ul>
</blockquote>
<p>At this point I’m not going to dive any deeper into message synchronization options and the different queues - I’ve got to make a cut somewhere - however in case someone is interested in finding bugs in these code areas I can highly recommend a look into chapter 8 of the amazing <a href="https://www.microsoftpressstore.com/store/windows-internals-part-2-9780135462409">Windows Internals, Part 2, 7th Edition</a>. I learned a lot from this book and can’t praise it enough!<br /></p>
<p>Finally, concerning the messaging details of ALPC, there is a last thing that hasn’t been detailed yet, which is the question of <em>how</em> is a message transported from a client to a server. It has been mentioned what kind of messages can be send, how the structure of a message looks like, what mechanism exist to synchronize and stall messages, but it hasn’t been detailed so far <em>how</em> a message get’s from one process to the other.<br />
You’ve got two options for this:</p>
<ul>
<li><strong>Double buffer mechanism</strong>: In this approach a message buffer is allocated in the sender’s and receiver’s (virtual) memory space and the message is copied from the sender’s (virtual) memory into the kernel’s (virtual) memory and from there into the receiver’s (virtual) memory. It’s called double buffer, because a buffer, containing the message, is allocated and copied twice (sender -> kernel & kernel -> receiver).</li>
<li><strong>Section object mechanism</strong>: Instead of allocating a buffer to store a message, client and server can also allocate a shared memory section, that can be accessed by both parties, map a view of that section - which basically means to reference a specific area of that allocated section - copy the message into the mapped view and finally send this view as a message attribute (discussed in the following chapter) to the receiver. The receiver can extract a pointer to the same view that the sender used through the view message attribute and read the data from this view.</li>
</ul>
<p>The main reason for using the <em>‘section object mechanism’</em> is to send large messages, as the length of messages send through the <em>‘double buffer mechanism’</em> have a hardcoded size limit of 65535 bytes. An error is thrown if this limit is exceeded in a message buffer. The function <code class="language-plaintext highlighter-rouge">AlpcMaxAllowedMessageLength()</code> can be used to get the maximum message buffer size, which might change in future versions of Windows.<br />
This <em>‘double buffer mechanism’</em> is what was used in the code snippet from above. Looking back a message buffer for the send and the received message has been implicitly allocated via the first three lines of code:</p>
<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="code"><pre><span class="n">ALPC_MESSAGE</span> <span class="n">pmSend</span><span class="p">,</span> <span class="n">pmReceived</span><span class="p">;</span> <span class="c1">// these are the message buffers</span>
<span class="n">RtlSecureZeroMemory</span><span class="p">(</span><span class="o">&</span><span class="n">pmSend</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">pmSend</span><span class="p">));</span>
<span class="n">RtlSecureZeroMemory</span><span class="p">(</span><span class="o">&</span><span class="n">pmReceived</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">pmReceived</span><span class="p">));</span>
</pre></td></tr></tbody></table></code></pre></figure>
<p>This message buffer has then been passed to the kernel in the call to <code class="language-plaintext highlighter-rouge">NtAlpcSendWaitReceivePort</code>, which copies the sending buffer into the receiving buffer on the other side.<br />
We could also dig into the kernel to figure out how an ALPC message (send via message buffers) actually looks like. Reversing the <code class="language-plaintext highlighter-rouge">NtAlpcSendWaitReceivePort</code> leads us to the kernel function <code class="language-plaintext highlighter-rouge">AlpcpReceiveMessage</code>, which eventually calls - for our code path - into <code class="language-plaintext highlighter-rouge">AlpcpReadMessageData</code>, where the copying of the buffer happens.<br />
<em>Side note: If you’re interested in all the reversing details I left out here check out my follow up post: <a href="/2022/05/29/Debugging-And-Reversing-ALPC.html">Debugging and Reversing ALPC</a></em></p>
<p>At the end of this road you’ll find a simple <a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-rtlcopymemory">RtlCopyMemory</a> call - which is just a macro for <a href="https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/memcpy-wmemcpy?view=msvc-170">memcpy</a> - that copies a bunch of bytes from one memory space into another - it’s not as fancy as one might have expected it, but that’s what it is ¯\<em>(ツ)</em>/¯.</p>
<p><img src="/public/img/2022-05-24-Offensive-Windows-IPC-3-ALPC/AlpcpReadMessageData.png" alt="Decompiled Function: AlpcpReadMessageData (Ghidra)" title="AlpcpReadMessageData decompiled in Ghidra" /></p>
<p>To see that in action I’ve put a breakpoint into the <code class="language-plaintext highlighter-rouge">AlpcpReadMessageData</code> function shown above for my ALPC server process. The breakpoint is triggered once my ALPC client connects and sends an initial message to the server. The message that the client sends is the: <code class="language-plaintext highlighter-rouge">Hello Server</code>. The annotated debug output is shown below:</p>
<p><img src="/public/img/2022-05-24-Offensive-Windows-IPC-3-ALPC/ALPC_Message_View.svg" alt="ALPC_Message_View.svg" title="Visualized double buffer messaging mechanism" /></p>
<p>These debug screens show what an ALPC message send through a message buffer looks like…just bytes in a process memory.<br />
Also note that the above screens is a visual representation of the ‘double buffer mechanism’ in it’s 2nd buffer copy stage, where a message is copied from kernel memory space into the receiver’s process memory space. The copy action from sender to kernel space has not been tracked as the breakpoint was only set for the receiver process.</p>
<h3 id="alpc-message-attributes">ALPC Message Attributes</h3>
<p>Alright, there’s one last piece that needs to be detailed before putting it all together, which is ALPC message attributes. I’ve mentioned message attributes a few times before, so here is what that means.<br />
When sending and receiving messages, via <code class="language-plaintext highlighter-rouge">NtAlpcSendWaitReceivePort</code>, client and server can both specify a set of attributes that they would like to send and/or receive. These set of attributes that one wants to send and the set of attributes that one wants to receive are passed to <code class="language-plaintext highlighter-rouge">NtAlpcSendWaitReceivePort</code> in two extra parameters, shown below:</p>
<p><img src="/public/img/2022-05-24-Offensive-Windows-IPC-3-ALPC/Code_NtAlpcSendWaitReceivePort.png" alt="Function: NtAlpcSendWaitReceivePort" title="NtAlpcSendWaitReceivePort function signature" /></p>
<p>The idea here is that as sender you can pass on additional information to a receiver and the receiver on the other end can specify what set of attributes he would like to get, meaning that not necessarily all extra information that was send is also exposed to the receiver.<br />
The following message attributes can be send and/or received:</p>
<ul>
<li><strong>Security Attribute</strong>: The security attribute holds security context information, which for example can be used to impersonate the sender of a message (detailed in the <a href="#impersonation-and-non-impersonation">Impersonation</a> section). This information is controlled and validated by the kernel. The structure of this attribute is shown below:</li>
</ul>
<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="code"><pre><span class="k">typedef</span> <span class="k">struct</span> <span class="nc">_ALPC_SECURITY_ATTR</span> <span class="p">{</span>
<span class="n">ULONG</span> <span class="n">Flags</span><span class="p">;</span>
<span class="n">PSECURITY_QUALITY_OF_SERVICE</span> <span class="n">pQOS</span><span class="p">;</span>
<span class="n">HANDLE</span> <span class="n">ContextHandle</span><span class="p">;</span>
<span class="p">}</span> <span class="n">ALPC_SECURITY_ATTR</span><span class="p">,</span> <span class="o">*</span> <span class="n">PALPC_SECURITY_ATTR</span><span class="p">;</span>
</pre></td></tr></tbody></table></code></pre></figure>
<ul>
<li><strong>View Attribute</strong>: As described towards the end of the <a href="#alpc-messaging-details">Messaging Details</a> chapter, this attribute can be used to pass over a pointer to a shared memory section, which can be used by the receiving party to read data from this memory section. The structure of this attribute is shown below:</li>
</ul>
<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
</pre></td><td class="code"><pre><span class="k">typedef</span> <span class="k">struct</span> <span class="nc">_ALPC_DATA_VIEW_ATTR</span> <span class="p">{</span>
<span class="n">ULONG</span> <span class="n">Flags</span><span class="p">;</span>
<span class="n">HANDLE</span> <span class="n">SectionHandle</span><span class="p">;</span>
<span class="n">PVOID</span> <span class="n">ViewBase</span><span class="p">;</span>
<span class="n">SIZE_T</span> <span class="n">ViewSize</span><span class="p">;</span>
<span class="p">}</span> <span class="n">ALPC_DATA_VIEW_ATTR</span><span class="p">,</span> <span class="o">*</span> <span class="n">PALPC_DATA_VIEW_ATTR</span><span class="p">;</span>
</pre></td></tr></tbody></table></code></pre></figure>
<ul>
<li><strong>Context Attribute</strong>: The context attribute stores pointers to user-specified context structures that have been assigned to a specific client (communication port) or to a specific message. The context structure can be any arbitrary structure, for example a unique number, and is meant to identify a client. The server can extract and reference the port structure to uniquely identify a client that send a message. An example of a port structure I used, can be found <a href="https://github.com/csandker/InterProcessCommunication-Samples/blob/master/ALPC/CPP-ALPC-Basic-Client-Server/CPP-Util/ALPC.h#L424-L428">here</a>. The kernel will set in the sequence number, message ID and callback ID to enable structured message handling (similar to TCP). This message attribute can always be extracted by the receiver of a message, the sender does not have to specify this and cannot prevent the receiver from accessing this. The structure of this attribute is shown below:</li>
</ul>
<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
</pre></td><td class="code"><pre><span class="k">typedef</span> <span class="k">struct</span> <span class="nc">_ALPC_CONTEXT_ATTR</span> <span class="p">{</span>
<span class="n">PVOID</span> <span class="n">PortContext</span><span class="p">;</span>
<span class="n">PVOID</span> <span class="n">MessageContext</span><span class="p">;</span>
<span class="n">ULONG</span> <span class="n">Sequence</span><span class="p">;</span>
<span class="n">ULONG</span> <span class="n">MessageId</span><span class="p">;</span>
<span class="n">ULONG</span> <span class="n">CallbackId</span><span class="p">;</span>
<span class="p">}</span> <span class="n">ALPC_CONTEXT_ATTR</span><span class="p">,</span> <span class="o">*</span> <span class="n">PALPC_CONTEXT_ATTR</span><span class="p">;</span>
</pre></td></tr></tbody></table></code></pre></figure>
<ul>
<li><strong>Handle Attribute</strong>: The handle attribute can be used to pass over a handle to a specific object, e.g. to a file. The receiver can use this handle to reference the object, e.g. in a call to <a href="https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-readfile">ReadFile</a>. The kernel will validate if the passed handle is valid and raise and error otherwise. The structure of this attribute is shown below:</li>
</ul>
<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
</pre></td><td class="code"><pre><span class="k">typedef</span> <span class="k">struct</span> <span class="nc">_ALPC_MESSAGE_HANDLE_INFORMATION</span> <span class="p">{</span>
<span class="n">ULONG</span> <span class="n">Index</span><span class="p">;</span>
<span class="n">ULONG</span> <span class="n">Flags</span><span class="p">;</span>
<span class="n">ULONG</span> <span class="n">Handle</span><span class="p">;</span>
<span class="n">ULONG</span> <span class="n">ObjectType</span><span class="p">;</span>
<span class="n">ACCESS_MASK</span> <span class="n">GrantedAccess</span><span class="p">;</span>
<span class="p">}</span> <span class="n">ALPC_MESSAGE_HANDLE_INFORMATION</span><span class="p">,</span> <span class="o">*</span> <span class="n">PALPC_MESSAGE_HANDLE_INFORMATION</span><span class="p">;</span>
</pre></td></tr></tbody></table></code></pre></figure>
<ul>
<li><strong>Token Attribute</strong>: The token attribute can be used to pass on limited information about the sender’s token. The structure of this attribute is shown below:</li>
</ul>
<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
</pre></td><td class="code"><pre><span class="k">typedef</span> <span class="k">struct</span> <span class="nc">_ALPC_TOKEN_ATTR</span>
<span class="p">{</span>
<span class="n">ULONGLONG</span> <span class="n">TokenId</span><span class="p">;</span>
<span class="n">ULONGLONG</span> <span class="n">AuthenticationId</span><span class="p">;</span>
<span class="n">ULONGLONG</span> <span class="n">ModifiedId</span><span class="p">;</span>
<span class="p">}</span> <span class="n">ALPC_TOKEN_ATTR</span><span class="p">,</span> <span class="o">*</span> <span class="n">PALPC_TOKEN_ATTR</span><span class="p">;</span>
</pre></td></tr></tbody></table></code></pre></figure>
<ul>
<li><strong>Direct Attribute</strong>: The direct attribute can be used to associate a <a href="https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-createeventa">created event</a> with a message. The receiver can retrieve the event created by the sender and signal it to let the sender know that the send message was received (especially useful for datagram requests). The structure of this attribute is shown below:</li>
</ul>
<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="code"><pre><span class="k">typedef</span> <span class="k">struct</span> <span class="nc">_ALPC_DIRECT_ATTR</span>
<span class="p">{</span>
<span class="n">HANDLE</span> <span class="n">Event</span><span class="p">;</span>
<span class="p">}</span> <span class="n">ALPC_DIRECT_ATTR</span><span class="p">,</span> <span class="o">*</span> <span class="n">PALPC_DIRECT_ATTR</span><span class="p">;</span>
</pre></td></tr></tbody></table></code></pre></figure>
<ul>
<li><strong>Work-On-Behalf-Of Attribute</strong>: This attribute can be used to send the <em>work ticket</em> that is associated with the sender. I haven’t played around with this so I can’t go in any more detail. The structure of this attribute is shown below:</li>
</ul>
<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="code"><pre><span class="k">typedef</span> <span class="k">struct</span> <span class="nc">_ALPC_WORK_ON_BEHALF_ATTR</span>
<span class="p">{</span>
<span class="n">ULONGLONG</span> <span class="n">Ticket</span><span class="p">;</span>
<span class="p">}</span> <span class="n">ALPC_WORK_ON_BEHALF_ATTR</span><span class="p">,</span> <span class="o">*</span> <span class="n">PALPC_WORK_ON_BEHALF_ATTR</span><span class="p">;</span>
</pre></td></tr></tbody></table></code></pre></figure>
<p><br />
The message attributes, how these are initialized and send was another thing that puzzled me when coding a sample ALPC server and client. So you don’t crash with the same problems that I had here are secret I learned about ALPC message attributes:</p>
<p>To get started one has to know that the structure for ALPC message attributes is the following:</p>
<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="code"><pre><span class="k">typedef</span> <span class="k">struct</span> <span class="nc">_ALPC_MESSAGE_ATTRIBUTES</span>
<span class="p">{</span>
<span class="n">ULONG</span> <span class="n">AllocatedAttributes</span><span class="p">;</span>
<span class="n">ULONG</span> <span class="n">ValidAttributes</span><span class="p">;</span>
<span class="p">}</span> <span class="n">ALPC_MESSAGE_ATTRIBUTES</span><span class="p">,</span> <span class="o">*</span> <span class="n">PALPC_MESSAGE_ATTRIBUTES</span><span class="p">;</span>
</pre></td></tr></tbody></table></code></pre></figure>
<p>Looking at this I initially thought you call the function <a href="https://github.com/csandker/InterProcessCommunication-Samples/blob/master/ALPC/CPP-ALPC-Basic-Client-Server/CPP-Util/ALPC.h#L292-L298">AlpcInitializeMessageAttribute</a> give it a reference to the above structure and the flag for the message attribute you want to send (all attributes are referenced by a flag value, <a href="https://github.com/csandker/InterProcessCommunication-Samples/blob/master/ALPC/CPP-ALPC-Basic-Client-Server/CPP-Util/ALPC.h#L12-L23">here’s the list from my code</a>) and the kernel then sets it all up for you. You then put the referenced structure into <a href="https://github.com/csandker/InterProcessCommunication-Samples/blob/master/ALPC/CPP-ALPC-Basic-Client-Server/CPP-Util/ALPC.h#L322-L332">NtAlpcSendWaitReceivePort</a>, repeat the process for every message you want to send and be all done.<br />
<strong>That is not the case</strong> and seems to be wrong on multiple levels. Only after I found <a href="https://twitter.com/hakril/status/1270835298944061441?s=20&t=CAlLQn_OV93l-glw-6JrXg">this twitter post</a> from 2020 and rewatched <a href="https://www.youtube.com/watch?v=UNpL5csYC1E">Alex’s SyScan’14 talk</a> once again (I re-watched this at least 20 times during my research) I came to what I currently believe is the right track. Let me first spot the errors in my initial believes before bundling the right course of actions:</p>
<ul>
<li><a href="https://github.com/csandker/InterProcessCommunication-Samples/blob/master/ALPC/CPP-ALPC-Basic-Client-Server/CPP-Util/ALPC.h#L292-L298">AlpcInitializeMessageAttribute</a> doesn’t do shit for you, it really only clears the <code class="language-plaintext highlighter-rouge">ValidAttributes</code> flag and sets the <code class="language-plaintext highlighter-rouge">AllocatedAttributes</code> flag according to your specified message attributes (so no kernel magic filling in data at all).<br />
<em>I’ll have to admit I spotted this early on from reverse engineering the function, but for some time I still hoped it would do some more as the name of the function was so promising.</em></li>
<li>To actually setup a message attribute properly you have to allocate the corresponding message structure and place it in a buffer after the <em>ALPC_MESSAGE_ATTRIBUTES</em> structure. So this is similar to an <em>ALPC_MESSAGE</em> where the actual message needs to be placed in a buffer after the <em>PORT_MESSAGE</em> structure.</li>
<li>It’s not the kernel that sets the <em>ValidAttributes</em> attribute for your <em>ALPC_MESSAGE_ATTRIBUTES</em> structure, you have to set this yourself. I figured this out by playing around with the structure and for some time I thought this was just a weird workaround, because why would <em>I</em> need to set the <code class="language-plaintext highlighter-rouge">ValidAttributes</code> field? As far as I’m concerned my attributes are always valid and shouldn’t it be the kernel’s task to check if they are valid.<br />
I took me another round of <a href="https://www.youtube.com/watch?v=UNpL5csYC1E">Alex’s SyScan’14 talk</a> to understand that..</li>
<li>You don’t setup the message attributes for every call to <a href="https://github.com/csandker/InterProcessCommunication-Samples/blob/master/ALPC/CPP-ALPC-Basic-Client-Server/CPP-Util/ALPC.h#L322-L332">NtAlpcSendWaitReceivePort</a>, you set all the message attributes up once and use the <em>ValidAttributes</em> flag before calling <a href="https://github.com/csandker/InterProcessCommunication-Samples/blob/master/ALPC/CPP-ALPC-Basic-Client-Server/CPP-Util/ALPC.h#L322-L332">NtAlpcSendWaitReceivePort</a> to specify which of all your set up attributes is valid for this very message you are sending now.</li>
</ul>
<p>To bundle this into useful knowledge, <strong>here’s how sending message attributes does work</strong> (in my current understanding):</p>
<ul>
<li>First of all you have two buffers: A buffer for message attributes you want to receive (in my code named: <code class="language-plaintext highlighter-rouge">MsgAttrReceived</code>) and a buffer for message attributes you want to send (in my code named: <code class="language-plaintext highlighter-rouge">MsgAttrSend</code>).</li>
<li>For the <code class="language-plaintext highlighter-rouge">MsgAttrReceived</code> buffer you just have to allocate a buffer that is large enough to hold the <em>ALPC_MESSAGE_ATTRIBUTES</em> structure plus all the message attributes that you want to receive. After allocating this buffer set the <code class="language-plaintext highlighter-rouge">AllocatedAttributes</code> attribute to the corresponding attribute(s) flag(s) value. This <code class="language-plaintext highlighter-rouge">AllocatedAttributes</code> value can be changed for every message you receive.<br />
For my sample server and client application I just want to always receive all attributes that the kernel could give me, therefore I set the buffer for the receiving attributes once at the beginning of my code as follows:</li>
</ul>
<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
</pre></td><td class="code"><pre><span class="n">pMsgAttrReceived</span> <span class="o">=</span> <span class="n">alloc_message_attribute</span><span class="p">(</span><span class="n">ALPC_MESSAGE_ATTRIBUTE_ALL</span><span class="p">);</span>
<span class="n">PALPC_MESSAGE_ATTRIBUTES</span> <span class="nf">alloc_message_attribute</span><span class="p">(</span><span class="n">ULONG</span> <span class="n">ulAttributeFlags</span><span class="p">)</span> <span class="p">{</span>
<span class="n">NTSTATUS</span> <span class="n">lSuccess</span><span class="p">;</span>
<span class="n">PALPC_MESSAGE_ATTRIBUTES</span> <span class="n">pAttributeBuffer</span><span class="p">;</span>
<span class="n">LPVOID</span> <span class="n">lpBuffer</span><span class="p">;</span>
<span class="n">SIZE_T</span> <span class="n">lpReqBufSize</span><span class="p">;</span>
<span class="n">SIZE_T</span> <span class="n">ulAllocBufSize</span><span class="p">;</span>
<span class="n">ulAllocBufSize</span> <span class="o">=</span> <span class="n">AlpcGetHeaderSize</span><span class="p">(</span><span class="n">ulAttributeFlags</span><span class="p">);</span> <span class="c1">// required size for specified attribues</span>
<span class="n">lpBuffer</span> <span class="o">=</span> <span class="n">HeapAlloc</span><span class="p">(</span><span class="n">GetProcessHeap</span><span class="p">(),</span> <span class="n">HEAP_ZERO_MEMORY</span><span class="p">,</span> <span class="n">ulAllocBufSize</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">GetLastError</span><span class="p">()</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
<span class="n">wprintf</span><span class="p">(</span><span class="s">L"[-] Failed to allocate memory for ALPC Message attributes.</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
<span class="k">return</span> <span class="nb">NULL</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">pAttributeBuffer</span> <span class="o">=</span> <span class="p">(</span><span class="n">PALPC_MESSAGE_ATTRIBUTES</span><span class="p">)</span><span class="n">lpBuffer</span><span class="p">;</span>
<span class="c1">// using this function to properly set the 'AllocatedAttributes' attribute</span>
<span class="n">lSuccess</span> <span class="o">=</span> <span class="n">AlpcInitializeMessageAttribute</span><span class="p">(</span>
<span class="n">ulAttributeFlags</span><span class="p">,</span> <span class="c1">// attributes</span>
<span class="n">pAttributeBuffer</span><span class="p">,</span> <span class="c1">// pointer to attributes structure</span>
<span class="n">ulAllocBufSize</span><span class="p">,</span> <span class="c1">// buffer size</span>
<span class="o">&</span><span class="n">lpReqBufSize</span>
<span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">NT_SUCCESS</span><span class="p">(</span><span class="n">lSuccess</span><span class="p">))</span> <span class="p">{</span>
<span class="k">return</span> <span class="nb">NULL</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">else</span> <span class="p">{</span>
<span class="c1">//wprintf(L"Success.\n");</span>
<span class="k">return</span> <span class="n">pAttributeBuffer</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></figure>
<p>[<a href="https://github.com/csandker/InterProcessCommunication-Samples/blob/master/ALPC/CPP-ALPC-Basic-Client-Server/CPP-ALPC-Basic-Server/CPP-ALPC-Basic-Server.cpp#L54">code</a>]</p>
<ul>
<li>For the <code class="language-plaintext highlighter-rouge">MsgAttrSend</code> buffer two more steps are involved. You have to allocate a buffer that is large enough to hold <em>ALPC_MESSAGE_ATTRIBUTES</em> structure plus all the message attributes that you want to send (just as before). You have to set the <code class="language-plaintext highlighter-rouge">AllocatedAttributes</code> attribute (just as before), but then you also have to initialize the message attributes (meaning creating the necessary structures and fill those with valid values) that you want to send and then finally set the <code class="language-plaintext highlighter-rouge">ValidAttributes</code> attribute. In my code I wanted to send different attributes in different messages so here’s how I did that:</li>
</ul>
<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
</pre></td><td class="code"><pre><span class="c1">// Allocate buffer and initialize the specified attributes</span>
<span class="n">pMsgAttrSend</span> <span class="o">=</span> <span class="n">setup_sample_message_attributes</span><span class="p">(</span><span class="n">hConnectionPort</span><span class="p">,</span> <span class="n">hServerSection</span><span class="p">,</span> <span class="n">ALPC_MESSAGE_SECURITY_ATTRIBUTE</span> <span class="o">|</span> <span class="n">ALPC_MESSAGE_VIEW_ATTRIBUTE</span> <span class="o">|</span> <span class="n">ALPC_MESSAGE_HANDLE_ATTRIBUTE</span><span class="p">);</span>
<span class="c1">// ...</span>
<span class="c1">// Before sending a message mark certain attributes as valid, in this case ALPC_MESSAGE_SECURITY_ATTRIBUTE </span>
<span class="n">pMsgAttrSend</span><span class="o">-></span><span class="n">ValidAttributes</span> <span class="o">|=</span> <span class="n">ALPC_MESSAGE_SECURITY_ATTRIBUTE</span>
<span class="n">lSuccess</span> <span class="o">=</span> <span class="n">NtAlpcSendWaitReceivePort</span><span class="p">(</span><span class="n">hConnectionPort</span><span class="p">,</span> <span class="p">...)</span>
<span class="c1">//...</span>
</pre></td></tr></tbody></table></code></pre></figure>
<p>[<a href="https://github.com/csandker/InterProcessCommunication-Samples/blob/master/ALPC/CPP-ALPC-Basic-Client-Server/CPP-ALPC-Basic-Server/CPP-ALPC-Basic-Server.cpp#L106">code</a>]</p>
<ul>
<li>There is an additional catch with the sending attribute buffer: <strong>You don’t have to allocate or initialize the context attribute or the token attribute</strong>. The kernel will always prepare these attributes and the receiver can always request them.</li>
<li>If you want to send multiple message attributes you will have a buffer that begins with the <em>ALPC_MESSAGE_ATTRIBUTES</em> followed by initialized structures for all the message attributes that you want.<br />
So how does the kernel know which attribute structure is which? The answer: You have to put the message attributes in a pre-defined order, which could be guessed from the value of their message attribute flags (from <a href="https://github.com/csandker/InterProcessCommunication-Samples/blob/master/ALPC/CPP-ALPC-Basic-Client-Server/CPP-Util/ALPC.h#L38">highest</a> to <a href="https://github.com/csandker/InterProcessCommunication-Samples/blob/master/ALPC/CPP-ALPC-Basic-Client-Server/CPP-Util/ALPC.h#L44">lowest</a>) or can also be found in the <em>_KALPC_MESSAGE_ATTRIBUTES</em> kernel structure:
<img src="/public/img/2022-05-24-Offensive-Windows-IPC-3-ALPC/ALPC_message_attribute_structure.png" alt="KALPC_MESSAGE_ATTRIBUTES structure" title="_KALPC_MESSAGE_ATTRIBUTES kernel structure" /></li>
<li>You might have noticed that the <strong>context</strong> and <strong>token attributes</strong> are not tracked in this structure and that is because the kernel will always provide these for any message, and hence does track them message independently.</li>
<li>Once send, the kernel will validate all the message attributes, fill in values (for example sequence numbers) or clear attributes that are invalid before offering these to the receiver.</li>
<li>Lastly the kernel will copy the attributes that the receiver specified as <code class="language-plaintext highlighter-rouge">AllocatedAttributes</code> into the receiver’s <code class="language-plaintext highlighter-rouge">MsgAttrReceived</code> buffer, from where they can be fetched by the receiver.</li>
</ul>
<p>All of the above might, hopefully, also get a little clearer if you go through <a href="https://github.com/csandker/InterProcessCommunication-Samples/tree/master/ALPC/CPP-ALPC-Basic-Client-Server">my code</a> and match these statements against where and how I used message attributes.</p>
<p>So far we’ve introduced various components of ALPC to describe how the ALPC messaging system works and what an ALPC message looks like. Let me conclude this chapter by putting a few of these components into perspective. The above description and structure of an ALPC message describe what an ALPC message looks like to sender and receiver, but one should be aware that the kernel is adding a lot more information to this message - in fact it takes the provided parts and places them in a much bigger kernel message structure - as you can see below:</p>
<p><img src="/public/img/2022-05-24-Offensive-Windows-IPC-3-ALPC/ALPC_KALPC_MESSAGE.png" alt="KALPC_MESSAGE structure" title="_KALPC_MESSAGE kernel structure" /></p>
<p>So the message here is: We’ve made a good understanding, but <strong>there is a lot more under the hood</strong> that we’ve not touched.</p>
<h2 id="putting-the-pieces-together-a-sample-application">Putting the pieces together: A Sample Application</h2>
<p>I have coded a sample ALPC client and server application as a playground to understand the different ALPC components. Feel free to browse and change the code to get your own feeling for ALPC. A few fair warnings about this code:</p>
<ul>
<li>The code is not intended to scale/grow well. The code is intended to be easily readable and guide through the main steps of sending/receiving ALPC messages.</li>
<li>This code is in absolutely no way even close to being performance, resource, or anything else optimized. It’s for learning.</li>
<li>I did not bother to take any effort to free buffers, messages or any other resources (which comes with a direct attack path, as described in section <a href="#unfreed-message-objects">Unfreed Message Objects</a>).</li>
</ul>
<p>Although there aren’t to many files to go through, let me point out a few notable lines of code:</p>
<ul>
<li>You can find how I set up sample messages attributes <a href="https://github.com/csandker/InterProcessCommunication-Samples/blob/master/ALPC/CPP-ALPC-Basic-Client-Server/CPP-ALPC-Basic-Server/CPP-ALPC-Basic-Server.cpp#L102-L106">here</a>.</li>
<li>You can find a call to <code class="language-plaintext highlighter-rouge">NtAlpcSendWaitReceivePort</code> that both sends and receives a message <a href="https://github.com/csandker/InterProcessCommunication-Samples/blob/master/ALPC/CPP-ALPC-Basic-Client-Server/CPP-ALPC-Basic-Client/CPP-ALPC-Basic-Client.cpp#L132-L141">here</a>.</li>
<li>You can find ALPC port flags, message attribute flags, message and connection flags <a href="https://github.com/csandker/InterProcessCommunication-Samples/blob/master/ALPC/CPP-ALPC-Basic-Client-Server/CPP-Util/ALPC.h#L12-L61">here</a>.</li>
</ul>
<p>And then finally here is what it looks like:</p>
<p><img src="/public/img/2022-05-24-Offensive-Windows-IPC-3-ALPC/ALPC_Sample_Client-Server.png" alt="Sample ALPC Client and Server Applications" title="Sample ALPC Client and Server Applications" /></p>
<h2 id="attack-surface">Attack Surface</h2>
<p>Before digging into the attack surface of ALPC communication channels, I’d like to point out an interesting conceptual weakness with ALPC communication that the below attack paths build on and that should be kept in mind to find further exploit potential.</p>
<p>Looking back at the <a href="#alpc-message-flow">ALPC Message Flow section</a> we can recall, that in order to allow for ALPC communication to occur a server has to open up an ALPC (connection) port, wait for incoming messages and then accept or decline these messages. Although an ALPC port is a securable kernel object and can as such be created with a <a href="https://docs.microsoft.com/en-us/windows/win32/secauthz/security-descriptors">Security Descriptor</a> that defines who can access and connect to it, most of the time the creating ALPC server process can’t (or want) to limit access based on a callee’s <a href="https://docs.microsoft.com/en-us/windows/win32/secauthz/security-identifiers">SID</a>. If you can’t (or want) limit access to your ALPC port by a <a href="https://docs.microsoft.com/en-us/windows/win32/secauthz/security-identifiers">SID</a>, the only option you have is to allow <em>Everyone</em> to connect to your port and make a accept/deny decision after a client has connected and messaged you. That in turn means that a lot of built-in ALPC servers do allow <em>Everyone</em> to connect and send a message to a server. Even if the server rejects a client right away, sending an initial message and some message attributes along with that message, might be enough to exploit a vulnerability.</p>
<p>Due to this communication architecture and the ubiquity of ALPC, exploiting ALPC is also an interesting vector to escape sandboxes.</p>
<h3 id="identify-targets">Identify Targets</h3>
<p>The first step in mapping the attack surface is to identify targets, which in our case are ALPC client or server processes.<br />
There are generally three routes that came to my mind of how to identify such processes:</p>
<ol>
<li>Identify ALPC port objects and map those to the owning processes</li>
<li>Inspect processes and determine if ALPC is used within them</li>
<li>Use Event Tracing for Windows (ETW) to list ALPC events</li>
</ol>
<p>All of these ways could be interesting, so let’s have a look at them…</p>
<p><strong>Find ALPC port objects</strong><br />
We’ve already seen the most straight forward way to identify ALPC port objects at the beginning of this post, which is to fire up <a href="https://docs.microsoft.com/en-us/sysinternals/downloads/winobj">WinObj</a> and spot ALPC objects by the ‘Type’ column. WinObj can’t give us more details so we head over to a <a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/debugger-download-tools">WinDbg</a> kernel debugger to inspect this ALPC port object:</p>
<p><img src="/public/img/2022-05-24-Offensive-Windows-IPC-3-ALPC/Inspect_AlpcPortObject_WinDbg.svg" alt="Inspect_AlpcPortObject_WinDbg" title="Inspecting ALPC port objects with WinDbg" /></p>
<p>In the above commands we used Windbg’s <a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/-object">!object</a> command to query the object manager for the <em>named</em> object in the specified path. This implicitly already told us that this ALPC port has to be an <strong>ALPC connection port</strong>, because communications ports are not named. In turn we can conclude that we can use <a href="https://docs.microsoft.com/en-us/sysinternals/downloads/winobj">WinObj</a> only to find <strong>ALPC connection ports</strong> and through these <em>only</em> ALPC server processes.<br />
Speaking of server processes: As shown above, one can use <a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/debugger-download-tools">WinDbg’s</a> undocumented <code class="language-plaintext highlighter-rouge">!alpc</code> command to display information about the ALPC port that we just identified. The output includes - alongside with a lot of other useful information, the owning server process of the port, which in this case is <em>svchost.exe</em>.<br />
Now that we know the address of the ALPC Port object we can use the <code class="language-plaintext highlighter-rouge">!alpc</code> command once again to display the active connections for this ALPC connection port:</p>
<p><img src="/public/img/2022-05-24-Offensive-Windows-IPC-3-ALPC/WinDbg_alpc_show_connections.png" alt="WinDbg_alpc_show_connections" title="Show ALPC port connections in WinDbg" /></p>
<p><em>Side note: The !alpc Windbg command is undocumented, but the outdated !lpc command, which existed in the LPC days, is documented <a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/-lpc">here</a> and has a timestamp from December 2021. This documentation page does mention that the !lpc command is outdated and that the !alpc command should be used instead, but the !alpc command syntax and options are completely different. But to be fair the !alpc command syntax is displayed in WinDbg if you enter any invalid !alpc command:</em></p>
<p><img src="/public/img/2022-05-24-Offensive-Windows-IPC-3-ALPC/WinDbg_alpc_command_syntax.png" alt="WinDbg_alpc_command_syntax" title="ALPC commands in WinDbg" /></p>
<p>Thanks to <a href="https://twitter.com/tiraniddo">James Forshaw</a> and his <a href="https://github.com/googleprojectzero/sandbox-attacksurface-analysis-tools/tree/25b183136e9a44ed148a0616875d83d785ef46de/NtObjectManager">NtObjectManager in .NET</a> we can also easily query the NtObjectManager in PowerShell to search for ALPC port objects, and even better <a href="https://twitter.com/tiraniddo">James</a> already provided the wrapper function for this via <a href="https://github.com/googleprojectzero/sandbox-attacksurface-analysis-tools/blob/25b183136e9a44ed148a0616875d83d785ef46de/NtObjectManager/RpcFunctions.ps1#L49">Get-AccessibleAlpcPort</a>.</p>
<p><img src="/public/img/2022-05-24-Offensive-Windows-IPC-3-ALPC/Get-AccessibleAlpcPort.png" alt="Get-AccessibleAlpcPort" title="Get-AccessibleAlpcPort command output" /></p>
<p><strong>Find ALPC used in processes</strong><br /></p>
<p>As always there are various ways to find ALPC port usage in processes, here are a few that came to mind:</p>
<ul>
<li>Similar to approaches in previous posts (<a href="https://csandker.io/2021/02/21/Offensive-Windows-IPC-2-RPC.html#rpc-servers">here</a>) one could use the <a href="https://docs.microsoft.com/en-us/cpp/build/reference/dumpbin-reference?view=msvc-170">dumpbin.exe</a> utility to list imported functions of executables and search therein for ALPC specific function calls.</li>
<li>As the above approach works with executable files on disk, but not with running processes, one could transfer the method used by <a href="https://docs.microsoft.com/en-us/cpp/build/reference/dumpbin-reference?view=msvc-170">dumpbin.exe</a> and parse the Import Address Table (IAT) of running processes to find ALPC specific function calls.</li>
<li>One could attach to running processes, query the open handles for this process and filter for those handles that point to ALPC ports.</li>
</ul>
<p>Once <a href="https://docs.microsoft.com/en-us/cpp/build/reference/dumpbin-reference?view=msvc-170">dumpbin.exe</a> is installed, which for examples comes with the Visual Studio C++ development suite, the following two PowerShell one-liners could be used to find <em>.exe</em> and <em>.dll</em> files that create or connec to an ALPC port:</p>
<figure class="highlight"><pre><code class="language-powershell" data-lang="powershell"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="code"><pre><span class="c">## Get ALPC Server processes (those that create an ALPC port)</span><span class="w">
</span><span class="n">Get-ChildItem</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="s2">"C:\Windows\System32\"</span><span class="w"> </span><span class="nt">-Include</span><span class="w"> </span><span class="p">(</span><span class="s1">'*.exe'</span><span class="p">,</span><span class="w"> </span><span class="s1">'*.dll'</span><span class="p">)</span><span class="w"> </span><span class="nt">-Recurse</span><span class="w"> </span><span class="nt">-ErrorAction</span><span class="w"> </span><span class="n">SilentlyContinue</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="o">%</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nv">$out</span><span class="o">=</span><span class="err">$</span><span class="p">(</span><span class="n">C:\</span><span class="s2">"Program Files (x86)"</span><span class="nx">\</span><span class="s2">"Microsoft Visual Studio 14.0"</span><span class="nx">\VC\bin\dumpbin.exe</span><span class="w"> </span><span class="nx">/IMPORTS:ntdll.dll</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">VersionInfo</span><span class="o">.</span><span class="nf">FileName</span><span class="p">);</span><span class="w"> </span><span class="kr">If</span><span class="p">(</span><span class="nv">$out</span><span class="w"> </span><span class="o">-like</span><span class="w"> </span><span class="s2">"*NtAlpcCreatePort*"</span><span class="p">){</span><span class="w"> </span><span class="n">Write-Host</span><span class="w"> </span><span class="s2">"[+] Executable creating ALPC Port: </span><span class="si">$(</span><span class="bp">$_</span><span class="o">.</span><span class="nf">VersionInfo</span><span class="o">.</span><span class="nf">FileName</span><span class="si">)</span><span class="s2">"</span><span class="p">;</span><span class="w"> </span><span class="n">Write-Output</span><span class="w"> </span><span class="s2">"[+] </span><span class="si">$(</span><span class="bp">$_</span><span class="o">.</span><span class="nf">VersionInfo</span><span class="o">.</span><span class="nf">FileName</span><span class="si">)</span><span class="se">`n`n</span><span class="s2"> </span><span class="si">$(</span><span class="nv">$out</span><span class="o">|%</span><span class="p">{</span><span class="s2">"</span><span class="bp">$_</span><span class="se">`n</span><span class="s2">"</span><span class="p">}</span><span class="si">)</span><span class="s2">"</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Out-File</span><span class="w"> </span><span class="nt">-FilePath</span><span class="w"> </span><span class="nx">NtAlpcCreatePort.txt</span><span class="w"> </span><span class="nt">-Append</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="c">## Get ALPC client processes (those that connect to an ALPC port)</span><span class="w">
</span><span class="n">Get-ChildItem</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="s2">"C:\Windows\System32\"</span><span class="w"> </span><span class="nt">-Include</span><span class="w"> </span><span class="p">(</span><span class="s1">'*.exe'</span><span class="p">,</span><span class="w"> </span><span class="s1">'*.dll'</span><span class="p">)</span><span class="w"> </span><span class="nt">-Recurse</span><span class="w"> </span><span class="nt">-ErrorAction</span><span class="w"> </span><span class="n">SilentlyContinue</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="o">%</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nv">$out</span><span class="o">=</span><span class="err">$</span><span class="p">(</span><span class="n">C:\</span><span class="s2">"Program Files (x86)"</span><span class="nx">\</span><span class="s2">"Microsoft Visual Studio 14.0"</span><span class="nx">\VC\bin\dumpbin.exe</span><span class="w"> </span><span class="nx">/IMPORTS:ntdll.dll</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">VersionInfo</span><span class="o">.</span><span class="nf">FileName</span><span class="p">);</span><span class="w"> </span><span class="kr">If</span><span class="p">(</span><span class="nv">$out</span><span class="w"> </span><span class="o">-like</span><span class="w"> </span><span class="s2">"*NtAlpcConnectPor*"</span><span class="p">){</span><span class="w"> </span><span class="n">Write-Host</span><span class="w"> </span><span class="s2">"[+] Executable connecting to ALPC Port: </span><span class="si">$(</span><span class="bp">$_</span><span class="o">.</span><span class="nf">VersionInfo</span><span class="o">.</span><span class="nf">FileName</span><span class="si">)</span><span class="s2">"</span><span class="p">;</span><span class="w"> </span><span class="n">Write-Output</span><span class="w"> </span><span class="s2">"[+] </span><span class="si">$(</span><span class="bp">$_</span><span class="o">.</span><span class="nf">VersionInfo</span><span class="o">.</span><span class="nf">FileName</span><span class="si">)</span><span class="se">`n`n</span><span class="s2"> </span><span class="si">$(</span><span class="nv">$out</span><span class="o">|%</span><span class="p">{</span><span class="s2">"</span><span class="bp">$_</span><span class="se">`n</span><span class="s2">"</span><span class="p">}</span><span class="si">)</span><span class="s2">"</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Out-File</span><span class="w"> </span><span class="nt">-FilePath</span><span class="w"> </span><span class="nx">NtAlpcConnectPort.txt</span><span class="w"> </span><span class="nt">-Append</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></figure>
<p><img src="/public/img/2022-05-24-Offensive-Windows-IPC-3-ALPC/AlpcProcesses_via_Dumpbin.png" alt="AlpcProcesses_via_Dumpbin" title="Executables using ALPC functionality" /></p>
<p>I did not code the 2nd option (parsing the IAT) - if you know a tool that does this <a href="https://twitter.com/0xcsandker">let me know</a>, but there is an easy, but <em>very slow</em> way to tackle option number 3 (find ALPC handles in processes) using the following WinDbg command: <code class="language-plaintext highlighter-rouge">!handle 0 2 0 ALPC Port</code></p>
<p><img src="/public/img/2022-05-24-Offensive-Windows-IPC-3-ALPC/Identify_ALPCPorts_via_WindbgHandle.png" alt="Identify_ALPCPorts_via_WindbgHandle" title="Identify handles to ALPC port objects using WinDbg" /></p>
<p>Be aware that this is very slow and will probably take a few hours to complete (I stopped after 10 minutes and only got around 18 handles).<br />
But once again thanks to <a href="https://twitter.com/tiraniddo">James Forshaw</a> and his <a href="https://github.com/googleprojectzero/sandbox-attacksurface-analysis-tools/tree/main/NtApiDotNet">NtApiDotNet</a> there is any easier way to code this yourself and speed up this process, plus we can also get some interesting ALPC stats…<br />
<em>You can find that tool <a href="https://github.com/csandker/InterProcessCommunication-Samples/tree/master/ALPC/CS-AlpcProcessHandles">here</a></em></p>
<p><img src="/public/img/2022-05-24-Offensive-Windows-IPC-3-ALPC/AlpcProcessHandles.svg" alt="AlpcProcessHandles.svg" title="Identify handles to ALPC port objects using NtApiDotNet" /></p>
<p>Note that this program does not run in kernel land, so I’d expect better results with the WinDbg command, but it does its job to list some ALPC ports used by various processes. By iterating over all processes that we have access to, we can also calculate some basic stats about ALPC usage, as shown above. These numbers are not 100% accurate, but with - on average - around 14 ALPC communication port handles used per process we can definitely conclude that ALPC is used quite frequently within Windows.</p>
<p>Once you identify a process that sounds like an interesting target WinDbg can be used again to dig deeper …</p>
<p><img src="/public/img/2022-05-24-Offensive-Windows-IPC-3-ALPC/AlpcProcess_via_Windbg.png" alt="AlpcProcess_via_Windbg" title="Find ALPC port objects in processes using WinDbg" /></p>
<p><strong>Use Event Tracing For Windows</strong><br /></p>
<p>Although ALPC is undocumented a <a href="https://docs.microsoft.com/en-us/windows/win32/etw/alpc">few ALPCs events</a> are exposed as Windows events that can be captured through Event Tracing for Windows (ETW). One of the tools that helps with ALPC events is <a href="https://github.com/zodiacon/ProcMonXv2">ProcMonXv2</a> by <a href="https://twitter.com/zodiacon">zodiacon</a>.</p>
<p><img src="/public/img/2022-05-24-Offensive-Windows-IPC-3-ALPC/ALPC_via_ProcMonXv2.png" alt="ALPC via ProcMonXv2" title="Identify ALPC communications with ETW using ProcMonXv2" /></p>
<p>After a few seconds of filtering for the five exposed ALPC events we get over 1000 events, another indication that ALPC is used quite frequently. But apart from that there is not much that ETW can offer in terms of insights into the ALPC communication channels, but anyhow, it did what it was intended to do: Identify ALPC targets.</p>
<h3 id="impersonation-and-non-impersonation">Impersonation and Non-Impersonation</h3>
<p>As with the previous post of the series (see <a href="/2021/02/21/Offensive-Windows-IPC-2-RPC.html#client-impersonation">here</a> & <a href="/2021/01/10/Offensive-Windows-IPC-1-NamedPipes.html#impersonating-a-named-pipe-client">here</a>) one interesting attack vector is impersonation of another party.<br />
<em>As last time, I’m not going to cover Impersonation again, but you’ll find all the explanation that you’ll need in the in the <a href="2021/01/10/Offensive-Windows-IPC-1-NamedPipes.html#impersonation">Impersonation section of the Named Pipe Post</a>.</em><br />
For ALPC communication the impersonation routines are bound to messages, which means that both client and server (aka. each communicating party) can impersonate the user on the other side. However, in order to allow for impersonation the impersonated communication partner has to allow to for impersonation to happen AND the impersonating communication partner needs to hold the <a href="https://docs.microsoft.com/en-us/windows/win32/secauthz/privilege-constants">SeImpersonate</a> privilege (it’s still a secured communication channel, right?)…<br />
Looking at the code there seem to be two options to fulfil the first condition, which is to allow being impersonated:</p>
<ul>
<li>The first option: Through the <code class="language-plaintext highlighter-rouge">PortAttributes</code>, e.g. like this:</li>
</ul>
<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
</pre></td><td class="code"><pre><span class="c1">// QOS</span>
<span class="n">SecurityQos</span><span class="p">.</span><span class="n">ImpersonationLevel</span> <span class="o">=</span> <span class="n">SecurityImpersonation</span><span class="p">;</span>
<span class="n">SecurityQos</span><span class="p">.</span><span class="n">ContextTrackingMode</span> <span class="o">=</span> <span class="n">SECURITY_STATIC_TRACKING</span><span class="p">;</span>
<span class="n">SecurityQos</span><span class="p">.</span><span class="n">EffectiveOnly</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="n">SecurityQos</span><span class="p">.</span><span class="n">Length</span> <span class="o">=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">SecurityQos</span><span class="p">);</span>
<span class="c1">// ALPC Port Attributs</span>
<span class="n">PortAttributes</span><span class="p">.</span><span class="n">SecurityQos</span> <span class="o">=</span> <span class="n">SecurityQos</span><span class="p">;</span>
<span class="n">PortAttributes</span><span class="p">.</span><span class="n">Flags</span> <span class="o">=</span> <span class="n">ALPC_PORTFLG_ALLOWIMPERSONATION</span><span class="p">;</span>
</pre></td></tr></tbody></table></code></pre></figure>
<ul>
<li>The second option: Through the <code class="language-plaintext highlighter-rouge">ALPC_MESSAGE_SECURITY_ATTRIBUTE</code> message attribute</li>
</ul>
<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="code"><pre><span class="n">pMsgAttrSend</span> <span class="o">=</span> <span class="n">setup_sample_message_attributes</span><span class="p">(</span><span class="n">hSrvCommPort</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">ALPC_MESSAGE_SECURITY_ATTRIBUTE</span><span class="p">);</span> <span class="c1">// setup security attribute</span>
<span class="n">pMsgAttrSend</span><span class="o">-></span><span class="n">ValidAttributes</span> <span class="o">|=</span> <span class="n">ALPC_MESSAGE_SECURITY_ATTRIBUTE</span><span class="p">;</span> <span class="c1">// specify it to be valid for the next message</span>
<span class="n">NtAlpcSendWaitReceivePort</span><span class="p">(...)</span> <span class="c1">// send the message</span>
</pre></td></tr></tbody></table></code></pre></figure>
<p><em>If you’re not super familiar with VC++/ALPC code, these snippets might not tell you anything, which is totally fine. The point here is: In theory there are two options to specify that you allow impersonation.</em><br />
However, there is a catch:</p>
<ul>
<li>If the server (the one with the connection port handle) wants to impersonate a client then impersonation is allowed if the client specified EITHER the first option OR the second (or both, but one option is sufficient).</li>
<li>However if the client wants to impersonate the server, then the server has to provide the 2nd option. In other words: The server has to send the <code class="language-plaintext highlighter-rouge">ALPC_MESSAGE_SECURITY_ATTRIBUTE</code> to allow the client to impersonate the server.</li>
</ul>
<p>I’ve looked at both routes: A server impersonating a client and a client impersonating a server.<br />
My first path was finding clients attempting to connect to a server port that does not exist in order to check for impersonation conditions. I tried various methods, but so far I haven’t figured a great way to identify such clients. I managed to use breakpoints in the kernel to manually spot some cases, but so far couldn’t find any interesting ones that would allow for client impersonation. Below is an example of the “ApplicationFrameHost.exe” trying to connect to an ALPC port that does not exist, which I could catch with my sample server, however, the process does not allow impersonation (and the application runs as my current user)…</p>
<p><img src="/public/img/2022-05-24-Offensive-Windows-IPC-3-ALPC/Client_Impersonation_Attempt.png" alt="Client Impersonation Attempt" title="Client impersonation attempt" /></p>
<p>Not a successful impersonation attempt, but at least it proves the idea.</p>
<p>Onto the other path: I located a bunch of ALPC connection ports using <a href="https://github.com/googleprojectzero/sandbox-attacksurface-analysis-tools/blob/25b183136e9a44ed148a0616875d83d785ef46de/NtObjectManager/RpcFunctions.ps1#L49">Get-AccessibleAlpcPort</a> as shown previously and instructed my ALPC client to connect to these in order to verify whether these a) allow me to connect, b) send me any actual message back and c) send impersonation message attributes along with a message. For all of the ALPC connection ports I checked at best I got some short initialization message with an <em>ALPC_MESSAGE_CONTEXT_ATTRIBUTE</em> back, which is not useful for impersonation, but at least once again it showcases the idea here:</p>
<p><img src="/public/img/2022-05-24-Offensive-Windows-IPC-3-ALPC//Server_Impersonation_Attempt.png" alt="Server Impersonation Attempt" title="Server impersonation attempt" /></p>
<p><strong>Server Non-Impersonation</strong></p>
<p>In the <a href="/2021/02/21/Offensive-Windows-IPC-2-RPC.html#server-non-impersonation">RPC Part</a> of the series I mentioned that it could also be interesting to connect to a server, that does use impersonation to change the security context of its thread to the security context of the calling client, but does not check if the impersonation succeeds or fails. In such a scenario the server might be tricked into executing tasks with its own - potentially elevated - security context.
As detailed in <a href="/2021/02/21/Offensive-Windows-IPC-2-RPC.html#server-non-impersonation">the post about RPC</a>, finding such occasions comes down to a case-by-base analysis of a specific ALPC server process you’re looking at. What you need for this is:</p>
<ul>
<li>A server process opening an ALPC port that your client can connect to</li>
<li>The server has to accept connection messages and must attempt to impersonate the server</li>
<li>The server must not check if the impersonation succeeds or fails</li>
<li>(For relevant cases the server must run in a different security context then your client, aka. different user or different integrity level)</li>
</ul>
<p>As of now I can’t think of a good way of automating or semi-automating the process of finding such targets. The only option that comes to mind is finding ALPC connection ports and reversing the hosting processes.<br />
<em>I’ll get this post updated if I stumble across anything interesting in this direction, but for the main part I wanted to re-iterate the attack path of failed impersonation attempts.</em></p>
<h3 id="unfreed-message-objects">Unfreed Message Objects</h3>
<p>As mentioned in the <a href="#ALPC_Message_Attributes">ALPC Message Attributes section</a> there are several message attributes that a client or server can send along with a message. One of these is the <em>ALPC_DATA_VIEW_ATTR</em> attribute that can be used to send information about a mapped view to the other communication party.<br />
To recall: This could for example be used to store larger messages or data in a shared view and send a handle to that shared view to the other party instead of using the <em>double-buffer messaging mechanism</em> to copy data from one memory space into another.<br />
The interesting bit here is that a shared view (or section as its called in Windows) is mapped into the process space of the receiver when being referenced in an <em>ALPC_DATA_VIEW_ATTR</em> attribute. The receiver could then do something with this section (if they are aware of it being mapped), but in the end the receiver of the message has to ensure that a mapped view is freed from its own memory space, and this requires a certain number of steps, which might not be followed correctly. If a receiver fails to free a mapped view, e.g. because it never expected to receive a view in the first place, the sender can send more and more views with arbitrary data to fill the receiver’s memory space with views of arbitrary data, which comes down to a <a href="https://en.wikipedia.org/wiki/Heap_spraying">Heap Spray</a> attack.</p>
<p>I only learned about this ALPC attack vector by (once again) listening to <a href="https://www.youtube.com/watch?v=UNpL5csYC1E">Alex Ionescu’s SyScan ALPC Talk</a> and I think there is no way to better phrase and showcase how this attack vector works then he does in this talk, so I’m not going to copy his content and words and just point you to <a href="https://www.youtube.com/watch?v=UNpL5csYC1E#t=32m13s">minute 32 of his talk</a>, where he starts to explain the attack. Also you want to see <a href="https://www.youtube.com/watch?v=UNpL5csYC1E#t=53m0s">minute 53 of his talk</a> for a demo of his heap spray attack.</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/UNpL5csYC1E?start=3180" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
<p><br /></p>
<p>The same logics applies with other ALPC message attributes, for example with handles that are send in <em>ALPC_MESSAGE_HANDLE_INFORMATION</em> via the ALPC handle attribute.</p>
<p>Finding vulnerable targets for this type of attacks is - once again - a case-by-case investigative process, where one has to:</p>
<ul>
<li>Find processes (of interest) using ALPC communication</li>
<li>Identify how a target process handles ALPC message attributes and especially if ALPC message attributes are freed</li>
<li>Get creative about options to abuse non-freed resources, where the obvious PoC option would be to exhaust process memory space</li>
</ul>
<p>Of course, another valid approach would be to pick a target and just flood it with views (as an example) to check if the result is a lot of shared memory regions being allocated within the target’s address space. A useful tool to inspect the memory regions of a process is <a href="https://docs.microsoft.com/en-us/sysinternals/downloads/vmmap">VMMap</a> from the <a href="https://docs.microsoft.com/en-us/sysinternals/">Sysinternals</a> suite, which is what I’ve used as a PoC below.<br />
As an example I’ve flooded my ALPC sample server with 20kb views as shown below:</p>
<p><img src="/public/img/2022-05-24-Offensive-Windows-IPC-3-ALPC/ALPC_Unfreed_Views.png" alt="ALPC_Unfreed_Views.png" title="Memory spraying a vulnerable ALPC application" /></p>
<p>This does work because I did not bother to make <em>any</em> effort to free <em>any</em> allocated attributes in my sample ALPC server.<br />
I’ve also randomly picked a few - <em>like four or five</em> - of Microsoft’s ALPC processes (that I identified using the above shown techniques), but the ones I picked do not seem to make the same mistake.<br />
Honestly, it might be valuable to check more processes for this, but as of know I have no use for this kind of bug other than crashing a process, which - if critical enough - might crash the OS as well (Denial of Service).</p>
<p><strong>Interesting Side note</strong>:<br />
In his talk <a href="https://twitter.com/aionescu">Alex Ionescu</a> mentions that the Windows Memory Manager allocates memory regions on 64kb boundaries, which means that whenever you allocate memory the Memory Manager places this memory at the start of the next available 64kb block. Which allows you, as an attacker, to create and map views of arbitrary size (preferably smaller than 64kb to make the memory exhaustion efficient) and the OS will map the view in the server’s memory and mark 64kb-YourViewSize as unusable memory, because it needs to align all memory allocation to 64kb boundaries. You want to see <a href="https://www.youtube.com/watch?v=UNpL5csYC1E#t=54m43s">minute 54 of Alex’s talk</a> to get a visual and verbal explanation of this effect.<br />
<a href="https://twitter.com/ChenCravat">Raymond Chen</a> explains the reasoning behind the 64kb granularity <a href="https://devblogs.microsoft.com/oldnewthing/20031008-00/?p=42223">here</a>.</p>
<p>At the end of the day memory exhaustion attacks are of course not the only viable option to use a memory/heap spray primitive, which people smarter than me can turn into a exploit path…</p>
<h2 id="conclusion">Conclusion</h2>
<p>ALPC is undocumented and quite complex, but as a motivational benefit: Vulnerabilities inside of ALPC can become very powerful as ALPC is ubiquitous within the Windows OS, all of the built-in high privileged processes use ALPC and due to its communication architecture it is an attractive target even from a sandbox perspective.</p>
<p>There is much more to ALPC than I have covered in this post. Potentially one could write an entire book about ALPC, but I hope to have at least touched the basics to get you started in getting interested in ALPC.</p>
<p>To get a first “Where and how much ALPC is in my PC”-impression I recommend starting <a href="https://github.com/zodiacon/ProcMonXv2">ProcMonXv2</a> (by <a href="https://twitter.com/zodiacon">zodiacon</a>) on your host to see thousands of ALPC events firing in a few seconds.</p>
<p><img src="/public/img/2022-05-24-Offensive-Windows-IPC-3-ALPC/ALPC_via_ProcMonXv2.png" alt="ALPC via ProcMonXv2" title="Identify ALPC communication using ProcMonXv2" /></p>
<p>To continue from there you might find my <a href="https://github.com/csandker/InterProcessCommunication-Samples/tree/master/ALPC/CPP-ALPC-Basic-Client-Server">ALPC client and server code</a> useful to play around with ALPC processes and to identify & exploit vulnerabilities within ALPC. If you find yourself coding and/or investigating ALPC make sure to check out the <a href="#references">reference</a> section for input on how others dealt with ALPC.</p>
<p>Finally as a last word and to conclude my recommendation from the beginning: If you feel like you could hear another voice & perspective on ALPC, I highly recommend to grab another beverage and an enjoy the following hour of <a href="https://twitter.com/aionescu">Alex Ionescu</a> talk about LPC, RPC and ALPC:</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/UNpL5csYC1E" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
<h2 id="appendix-a-the-use-of-connection-and-communication-ports">Appendix A: The use of connection and communication ports</h2>
<p>When looking into ALPC I initially thought that a server listens on its <strong>communication port</strong>, which it receives when accepting a client connection via <a href="https://github.com/csandker/InterProcessCommunication-Samples/blob/master/ALPC/CPP-ALPC-Basic-Client-Server/CPP-Util/ALPC.h#L307-L320">NtAlpcConnectPort</a>. This would have made sense, since it’s called <u>communication</u> port. However, listening for incoming messages on the server’s communication port resulted in a blocking call to <a href="https://github.com/csandker/InterProcessCommunication-Samples/blob/master/ALPC/CPP-ALPC-Basic-Client-Server/CPP-Util/ALPC.h#L322-L332">NtAlpcSendWaitReceivePort</a> that never came back with a message.<br />
So my assumption about the server’s ALPC <em>communication</em> port must have been wrong, which puzzled me, since the client on the other side does get messages on his communication port. I hung on this question for a while until I reached out to <a href="https://twitter.com/aionescu">Alex Ionescu</a> to ask him about this and I learned that my assumption was indeed incorrect, but to be more precise it has become incorrect over time: Alex explained to me that the idea I had (server listens and sends messages on its communication port) was the way that LPC (the predecessor of ALPC) was designed to work. This design however would force you to listen on a growing number of communication ports with each new client the server accepts. Imagine a server has 100 clients talking to it, then the server needs to listen on 100 communication ports to get client messages, which often resulted in creating 100 threads, where each thread would communicate with a different client. This was deemed inefficient and a much more efficient solution was to have a single thread listening (and sending) on the server’s connection port, where all messages are being send to this connection port.<br />
That in turn means: A server accepts a client connection, receives a handle to a client’s communication port, but still uses the server’s connection port handle in calls to <a href="https://github.com/csandker/InterProcessCommunication-Samples/blob/master/ALPC/CPP-ALPC-Basic-Client-Server/CPP-Util/ALPC.h#L322-L332">NtAlpcSendWaitReceivePort</a> in order to send and receive messages from all connected clients.</p>
<p>Does that mean that the server’s communication port is obsolete then (and this was my follow up question to <a href="https://twitter.com/aionescu">Alex</a>)? His answer, once again, made perfect sense and cleared my understanding of ALPC: A server’s per client communication port is used internally by the OS to tie a message, send by a specific client, to this client’s specific communication port. This allows the OS to tie a special context structure to each client communication port that can be used to identify the client. This special context structure is the <em>PortContext</em>, which can be any arbitrary structure, that can be passed to <a href="https://github.com/csandker/InterProcessCommunication-Samples/blob/master/ALPC/CPP-ALPC-Basic-Client-Server/CPP-Util/ALPC.h#L307-L320">NtAlpcAcceptConnectPort</a> and which can later be extracted from the any message with the <em>ALPC_CONTEXT_ATTR</em> message attribute.<br />
That means: When a server listens on its connection port it receives messages from all clients, but if it wants to know which client send the message, the server can get the port context structure (through the <em>ALPC_CONTEXT_ATTR</em> message attribute), that it assigned to this client upon accepting the connection, and the OS will fetch that context structure from the internally preserved client communication port.</p>
<p>This far we can conclude that the server’s per-client communication port is still important for the OS and still has its place and role in the ALPC communication structure. That does, however, not answer the question why the server would actually need a handle to each-clients communication port (because the client’s <em>PortContext</em> can be extracted from a message, which is received by using the connection port handle).<br />
The answer here is <strong>impersonation</strong>. When the server wants to impersonate a client it needs to pass the client’s communication port to <a href="https://github.com/csandker/InterProcessCommunication-Samples/blob/master/ALPC/CPP-ALPC-Basic-Client-Server/CPP-Util/ALPC.h#L352-L357">NtAlpcImpersonateClientOfPort</a>. The reason for this is that the security context information that are needed to perform the impersonation are bound (if allowed by the client) to the client’s communication port. It would make no sense to attach these information to the connection port, because all clients use this connection port, whereas each client has it own unique communication port for each server.<br />
Therefore: If you want to impersonate your clients you want to keep each client’s communication port handle.</p>
<h2 id="references">References</h2>
<p>Below are a few resources that I found helpful to learn and dig into ALPC.</p>
<p><strong>Reference Projects that make use of ALPC</strong></p>
<ul>
<li><a href="https://github.com/microsoft/terminal/blob/main/src/interactivity/onecore/ConIoSrvComm.cpp">https://github.com/microsoft/terminal/blob/main/src/interactivity/onecore/ConIoSrvComm.cpp</a></li>
<li><a href="https://github.com/DownWithUp/ALPC-Example">https://github.com/DownWithUp/ALPC-Example</a></li>
<li><a href="https://github.com/DynamoRIO/drmemory">https://github.com/DynamoRIO/drmemory</a></li>
<li><a href="https://github.com/hakril/PythonForWindows">https://github.com/hakril/PythonForWindows</a></li>
<li><a href="https://docs.rs/">https://docs.rs/</a></li>
<li><a href="https://github.com/googleprojectzero/sandbox-attacksurface-analysis-tools">https://github.com/googleprojectzero/sandbox-attacksurface-analysis-tools</a></li>
<li><a href="https://processhacker.sourceforge.io/doc/ntlpcapi_8h.html">https://processhacker.sourceforge.io/doc/ntlpcapi_8h.html</a></li>
<li><a href="https://github.com/bnagy/w32">https://github.com/bnagy/w32</a></li>
<li><a href="https://github.com/taviso/ctftool">https://github.com/taviso/ctftool</a></li>
</ul>
<p><strong>References to ALPC implementation details</strong></p>
<ul>
<li><a href="https://github.com/googleprojectzero/sandbox-attacksurface-analysis-tools/blob/main/NtApiDotNet/NtAlpcNative.cs">https://github.com/googleprojectzero/sandbox-attacksurface-analysis-tools/blob/main/NtApiDotNet/NtAlpcNative.cs</a></li>
<li><a href="https://processhacker.sourceforge.io/doc/ntlpcapi_8h.html#a10ebe9ef7db909fbe2f152fb82a352bf">https://processhacker.sourceforge.io/doc/ntlpcapi_8h.html</a></li>
<li><a href="https://github.com/hakril/PythonForWindows/blob/master/windows/generated_def/windef.py">https://github.com/hakril/PythonForWindows/blob/master/windows/generated_def/windef.py</a></li>
</ul>
<p><strong>Talks about ALPC</strong></p>
<ul>
<li><a href="https://www.youtube.com/watch?v=UNpL5csYC1E">Youtube: SyScan’14 Singapore: All About The Rpc, Lrpc, Alpc, And Lpc In Your Pc By Alex Ionescu</a></li>
<li><a href="https://infocon.org/cons/SyScan/SyScan%202014%20Singapore/SyScan%202014%20presentations/SyScan2014_AlexIonescu_AllabouttheRPCLRPCALPCandLPCinyourPC.pdf">Slides: SyScan’14 Singapore: All About The Rpc, Lrpc, Alpc, And Lpc In Your Pc By Alex Ionescu</a></li>
<li><a href="https://www.youtube.com/watch?v=D-F5RxZ_yXc">Youtube: Hack.lu 2017 A view into ALPC-RPC by Clement Rouault and Thomas Imbert</a></li>
<li><a href="https://conference.hitb.org/hitbsecconf2014kul/materials/D2T1%20-%20Ben%20Nagy%20-%20ALPC%20Fuzzing%20Toolkit.pdf">Slides: ALPC Fuzzing Toolkit</a></li>
</ul>
<p><strong>LPC References</strong>:</p>
<ul>
<li><a href="https://github.com/avalon1610/LPC">https://github.com/avalon1610/LPC</a></li>
</ul>Contents:Active Directory Spotlight: Trusts - Part 2. Operational Guidance2021-10-10T08:30:00+00:002021-10-10T08:30:00+00:00https://csandker.io//2021/10/10/Active-Directory-Spotlight-Trusts-Part2-Operational-Guidance<p><em>I’ve published this blog post on my employeer’s blog. This entry is used just to align and keep track of the chronicle.</em></p>
<p>Read the blog post here:</p>
<blockquote>
<p><strong><a href="https://www.securesystems.de/blog/active-directory-spotlight-trusts-part-2-operational-guidance/">https://www.securesystems.de/blog/active-directory-spotlight-trusts-part-2-operational-guidance/</a></strong></p>
</blockquote>I’ve published this blog post on my employeer’s blog. This entry is used just to align and keep track of the chronicle.Active Directory Spotlight: Trusts - Part 1. The Mechanics2021-10-10T08:00:00+00:002021-10-10T08:00:00+00:00https://csandker.io//2021/10/10/Active-Directory-Spotlight-Trusts-Part1-The-Mechanics<p><em>I’ve published this blog post on my employeer’s blog. This entry is used just to align and keep track of the chronicle.</em></p>
<p>Read the blog post here:</p>
<blockquote>
<p><strong><a href="https://www.securesystems.de/blog/active-directory-spotlight-trusts-part-1-the-mechanics/">https://www.securesystems.de/blog/active-directory-spotlight-trusts-part-1-the-mechanics/</a></strong></p>
</blockquote>I’ve published this blog post on my employeer’s blog. This entry is used just to align and keep track of the chronicle.Offensive Windows IPC Internals 2: RPC2021-02-21T08:00:00+00:002021-02-21T08:00:00+00:00https://csandker.io//2021/02/21/Offensive-Windows-IPC-2-RPC<h2 class="no_toc toc-header" id="contents">Contents:</h2>
<ul id="markdown-toc">
<li><a href="#the-series" id="markdown-toc-the-series">The Series</a></li>
<li><a href="#introduction" id="markdown-toc-introduction">Introduction</a></li>
<li><a href="#history" id="markdown-toc-history">History</a></li>
<li><a href="#rpc-messaging" id="markdown-toc-rpc-messaging">RPC Messaging</a> <ul>
<li><a href="#rpc-protocol-sequence" id="markdown-toc-rpc-protocol-sequence">RPC Protocol Sequence</a></li>
<li><a href="#rpc-interfaces" id="markdown-toc-rpc-interfaces">RPC Interfaces</a></li>
<li><a href="#rpc-binding" id="markdown-toc-rpc-binding">RPC Binding</a></li>
<li><a href="#anonymous--authenticated-bindings" id="markdown-toc-anonymous--authenticated-bindings">Anonymous & Authenticated Bindings</a> <ul>
<li><a href="#registration-flags" id="markdown-toc-registration-flags">Registration Flags</a></li>
<li><a href="#security-callbacks" id="markdown-toc-security-callbacks">Security Callbacks</a></li>
<li><a href="#authenticated-bindings" id="markdown-toc-authenticated-bindings">Authenticated Bindings</a></li>
</ul>
</li>
<li><a href="#well-known-vs-dynamic-endpoints" id="markdown-toc-well-known-vs-dynamic-endpoints">Well-known vs Dynamic Endpoints</a></li>
<li><a href="#rpc-communication-flow" id="markdown-toc-rpc-communication-flow">RPC Communication Flow</a></li>
<li><a href="#sample-implementation" id="markdown-toc-sample-implementation">Sample Implementation</a></li>
</ul>
</li>
<li><a href="#access-matrix" id="markdown-toc-access-matrix">Access Matrix</a></li>
<li><a href="#attack-surface" id="markdown-toc-attack-surface">Attack Surface</a> <ul>
<li><a href="#finding-interesting-targets" id="markdown-toc-finding-interesting-targets">Finding Interesting Targets</a> <ul>
<li><a href="#rpc-servers" id="markdown-toc-rpc-servers">RPC Servers</a></li>
<li><a href="#rpc-clients" id="markdown-toc-rpc-clients">RPC Clients</a></li>
</ul>
</li>
<li><a href="#unauthorized-access" id="markdown-toc-unauthorized-access">Unauthorized Access</a></li>
<li><a href="#client-impersonation" id="markdown-toc-client-impersonation">Client Impersonation</a></li>
<li><a href="#server-non-impersonation" id="markdown-toc-server-non-impersonation">Server Non-Impersonation</a></li>
<li><a href="#mitm-authenticated-ntlm-connections" id="markdown-toc-mitm-authenticated-ntlm-connections">MITM Authenticated NTLM Connections</a></li>
<li><a href="#mitm-authenticated-gss_negotiate-connections" id="markdown-toc-mitm-authenticated-gss_negotiate-connections">MITM Authenticated GSS_NEGOTIATE Connections</a></li>
</ul>
</li>
<li><a href="#references" id="markdown-toc-references">References</a></li>
</ul>
<h2 id="the-series">The Series</h2>
<p>This is part 2 of my series: Offensive Windows IPC Internals.<br />
If you missed part one and want to take a look, you’ll find it here: <a href="https://csandker.io/2021/01/10/Offensive-Windows-IPC-1-NamedPipes.html">Offensive Windows IPC Internals 1: Named Pipes</a>.<br />
Part 2 was originally planned to be about LPC & ALPC, but as it turns out it’s quite time consuming to dig out all the undocumented bits and tricks about these technologies. Therefore i made the discussion to publish my knowledge about RPC first before turning my head towards ALPC once again.</p>
<p>The reason why i originally planed to publish LPC & ALPC before RPC is because RPC uses ALPC under the hood when used locally and even more: RPC is the intended solution for fast local inter process communication as RPC can be instructed to process local communication via a special ALPC protocol sequence (but you’ll find that out while reading on).</p>
<p>Anyhow, the lesson here is (i guess) that sometimes its better to pause on a thing and get your head cleared up and make progress with something else before you get lost in something that is just not ready to reveal its mysteries to you.</p>
<p>Get a coffee and a comfy chair and buckle up for RPC…</p>
<h2 id="introduction">Introduction</h2>
<p><strong>R</strong>emote <strong>P</strong>rocedure <strong>C</strong>alls (RPC) is a technology to enable data communication between a client and a server across process and machine boundaries (network communication). Therefore RPC is an Inter Process Communication (<strong>IPC</strong>) technology. Other technologies in this category are for example LPC, ALPC or <a href="https://csandker.io/2021/01/10/Offensive-Windows-IPC-1-NamedPipes.html">Named Pipes</a>.<br />
As the name and this category implies RPC is used to make calls to remote servers to exchange/deliver data or to trigger a remote routine. The term “remote” in this case does not describe a requirement for the communication. An RPC server does not has to be on a remote machine, and in theory does not even has to be in a different process (although this would make sense).<br />
In theory you could implement a RPC server & client in DLLs, load them into the same process and exchange messages, but you wouldn’t gain much as the messages would still be routed through other components outside of your process (such as the kernel, but more on this later) and you would try to make use of an “Inter” Process Communication technology for “Intra” Process Communication.<br />
Moreover a RPC server does not need to be on a remote machine, but could as well be called from a local client.</p>
<p>Within this blog post you can join me in discovering the insides of RPC, how it works & operates and how to implement and attack RPC clients and servers.<br />
This post is is made from an offensive view point and tries to cover the most relevant aspects the attack surface of RPC from an attackers perspective. A more defensive geared view on RPC can for example be found at <a href="https://ipc-research.readthedocs.io/en/latest/subpages/RPC.html">https://ipc-research.readthedocs.io/en/latest/subpages/RPC.html</a> by <a href="https://twitter.com/jsecurity101">Jonathan Johnson</a></p>
<p>The below post will contain some references to code from my sample implementations, all of this code can be found here:<br />
<a href="https://github.com/csandker/InterProcessCommunication-Samples/tree/master/RPC/CPP-RPC-Client-Server">https://github.com/csandker/InterProcessCommunication-Samples/tree/master/RPC/CPP-RPC-Client-Server</a></p>
<h2 id="history">History</h2>
<p>Microsoft’s RPC implementation is based on the RPC implementation of the Distributed Computing Environment (DCE) standard developed by the Open Software Foundation (OSF) in 1993.</p>
<blockquote>
<p>“One of the key companies that contributed [to the DCE implementation] was Apollo Computer, who brought in NCA – ‘Network Computing Architecture’ which became Network Computing System (NCS) and then a major part of DCE/RPC itself”<br />
<em>Source: <a href="https://kganugapati.wordpress.com/tag/msrpc/">https://kganugapati.wordpress.com/tag/msrpc/</a></em></p>
</blockquote>
<p>Microsoft hired Paul Leach (in 1991), one of the founding Engineers of Apollo, which <em>might be</em> how RPC came into Windows.</p>
<p>Microsoft adjusted the DCE model to fit their programming scheme, <b>based the communication of RPC on Named Pipes</b> and brought their implementation to daylight in Windows 95.<br />
Back in the days you could have wondered why they based the communication on Named Pipes, because Microsoft just came up with a new technology called Local Procedure Call (<b>LPC</b>) in 1994 and it sounds like it would have made sense to base a technology called <u>Remote</u> Procedure Call on something called <u>Local</u> Procedure call, right?… Well yes LPC would have been the logical choice (and I would guess they initially went with LPC), but LPC had a crucial flaw: It didn’t support (and still doesn’t) asynchronous calls (more on this when i finally finish my LPC/ALPC post…), which is why Microsoft based it on Named Pipes.</p>
<p>As we’ll see in a moment (section <a href="#rpc-protocol-sequence">RPC Protocol Sequence</a>) when implementing routines with RPC the developer needs to tell the RPC library what ‘protocol’ to use for transportation. The original DCE/RCP standard already had defined ‘ncacn_ip_tcp’ and ‘ncadg_ip_udp’ for TCP and UDP connections. Microsoft added ‘ncacn_np’ for their implementation based on Named Pipes (transported through the SMB protocol).</p>
<h2 id="rpc-messaging">RPC Messaging</h2>
<p>RPC is a client-server technology with messaging architecture similar to COM (Component Object Model), which on a high level consists of the following three components:</p>
<ul>
<li>A server & client process that are responsible for registering an RPC interface and associated binding information (more on this later on)</li>
<li>Server & client stubs that are responsible for marshalling incoming and outgoing data</li>
<li>The server’s & client’s RPC runtime library (rpcrt4.dll), which takes the stub data and sends them over the wire using the specified protocol (examples and details will follow)</li>
</ul>
<p>A visual overview of this message architecture can be found at <a href="https://docs.microsoft.com/en-us/windows/win32/rpc/how-rpc-works">https://docs.microsoft.com/en-us/windows/win32/rpc/how-rpc-works</a> as shown below:</p>
<p><img src="/public/img/2021-02-21-Offensive-Windows-IPC-2-RPC/RPC_Message_Flow.png" alt="RPC Message Flow" /></p>
<p>Later on, in section <a href="#rpc-communication-flow">RPC Communication Flow</a>, i will provide an overview of the steps involved from creating an RPC server to sending a message, but before we can dive into that we need to clarify a few RPC terminology bits.</p>
<p>Bare with me here while we dig into the insides of RPC. The following things are essential to know in order to to get along with RPC.<br />
If you get lost in new terms and API calls that you just can’t get in line you can always jump ahead to the <a href="#rpc-communication-flow">RPC Communication Flow</a> section to get an idea of where these thing belong in the communication chain.</p>
<h3 id="rpc-protocol-sequence">RPC Protocol Sequence</h3>
<p>The <b>RPC Protocol Sequence</b> is a constant string that defines which protocol the RPC runtime should use to transfer messages.<br />
This string defines which RPC protocol, transport and network protocol should be used.<br />
Microsoft supports the following three RPC protocols:</p>
<ul>
<li>Network Computing Architecture connection-oriented protocol (NCACN)</li>
<li>Network Computing Architecture datagram protocol (NCADG)</li>
<li>Network Computing Architecture local remote procedure call (NCALRPC)</li>
</ul>
<p>In most scenarios where a connection is made across system boundaries you will find NCACN, whereas NCALRPC is recommended for local RPC communication.</p>
<p>The protocol sequence is a defined constant string assembled from the above parts, e.g. ncacn_ip_tcp for a connection-oriented communication based on TCP packets.<br />
The full list of RPC protocol sequence constants can be found at: <a href="https://docs.microsoft.com/en-us/windows/win32/rpc/protocol-sequence-constants">https://docs.microsoft.com/en-us/windows/win32/rpc/protocol-sequence-constants</a>.</p>
<p>The most relevant protocol sequences are shown below:</p>
<table>
<thead>
<tr>
<th style="text-align: left">Constant/Value</th>
<th style="text-align: left">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left">ncacn_ip_tcp</td>
<td style="text-align: left">Connection-oriented Transmission Control Protocol/Internet Protocol (TCP/IP)</td>
</tr>
<tr>
<td style="text-align: left">ncacn_http</td>
<td style="text-align: left">Connection-oriented TCP/IP using Microsoft Internet Information Server as HTTP proxy</td>
</tr>
<tr>
<td style="text-align: left">ncacn_np</td>
<td style="text-align: left">Connection-oriented named pipes (via SMB.)</td>
</tr>
<tr>
<td style="text-align: left">ncadg_ip_udp</td>
<td style="text-align: left">Datagram (connectionless) User Datagram Protocol/Internet Protocol (UDP/IP)</td>
</tr>
<tr>
<td style="text-align: left">ncalrpc</td>
<td style="text-align: left">Local Procedure Calls (post Windows Vista via ALPC)</td>
</tr>
</tbody>
</table>
<h3 id="rpc-interfaces">RPC Interfaces</h3>
<p>In order to establish a communication channel the RPC runtime needs to know what methods (aka. “functions”) and parameters your server offers and what data your client is sending. These information are defined in a so called “Interface”.<br />
<em>Side note: If you’re familiar with interfaces in COM, this is the same thing.</em></p>
<p>To get an idea of how an interface could be defined, let’s take this example from my <a href="https://github.com/csandker/InterProcessCommunication-Samples/blob/master/RPC/CPP-RPC-Client-Server/RPC-Interface1/Interface1-Implicit.idl">Sample Code</a>:</p>
<p><strong>Interface1.idl</strong></p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span>
<span class="c1">// UUID: A unique identifier that distinguishes this</span>
<span class="c1">// interface from other interfaces.</span>
<span class="n">uuid</span><span class="p">(</span><span class="mi">9510</span><span class="n">b60a</span><span class="o">-</span><span class="mi">2</span><span class="n">eac</span><span class="o">-</span><span class="mi">43</span><span class="n">fc</span><span class="o">-</span><span class="mi">8077</span><span class="o">-</span><span class="n">aaefbdf3752b</span><span class="p">),</span>
<span class="c1">// This is version 1.0 of this interface.</span>
<span class="n">version</span><span class="p">(</span><span class="mf">1.0</span><span class="p">),</span>
<span class="c1">// Using an implicit handle here named hImplicitBinding:</span>
<span class="n">implicit_handle</span><span class="p">(</span><span class="n">handle_t</span> <span class="n">hImplicitBinding</span><span class="p">)</span>
<span class="p">]</span>
<span class="n">interface</span> <span class="n">Example1</span> <span class="c1">// The interface is named Example1</span>
<span class="p">{</span>
<span class="c1">// A function that takes a zero-terminated string.</span>
<span class="kt">int</span> <span class="n">Output</span><span class="p">(</span>
<span class="p">[</span><span class="n">in</span><span class="p">,</span> <span class="n">string</span><span class="p">]</span> <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">pszOutput</span><span class="p">);</span>
<span class="kt">void</span> <span class="n">Shutdown</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>
<p>The first thing to note is that interfaces are defined in an Interface Definition Language (IDL) file. The definitions in this will later on be compiled by the Microsoft IDL compiler (midl.exe) into header and source code files that can be used by the server and client.<br />
The interface header is rather self explanatory with the given comments - ignore the <em>implicit_handle</em> instruction for now, we get into implicit and explicit handles shortly.<br />
The body of the interface describes the methods that this interfaces exposes, their return values and their parameters. The <code class="language-plaintext highlighter-rouge">[in, string]</code> statement within parameter definition of the <em>Output</em> function is not mandatory but aids the understanding of what this parameter is used for.</p>
<p>Side note: You could also specify various interface attributes in an Application Configuration File (ACF). Some of these such as the type of binding (explicit vs. implicit) can be placed in the IDL file, but for more complex interfaces you might want to add an extra ACF file per interface.</p>
<h3 id="rpc-binding">RPC Binding</h3>
<p>Once your client connects to an RPC server (we’ll get into how this is done later on) you create what Microsoft calls a “Binding”. Or to put it with <a href="https://docs.microsoft.com/en-us/windows/win32/rpc/binding-handles">Microsoft’s words</a>:</p>
<blockquote>
<p>Binding is the process of creating a logical connection between a client program and a server program. The information that composes the binding between client and server is represented by a structure called a binding handle.</p>
</blockquote>
<p>The terminology of <strong>binding handles</strong> gets clearer once we put some context on it. Technically there three types of binding handles:</p>
<ul>
<li>Implicit</li>
<li>Explicit</li>
<li>Automatic</li>
</ul>
<p>Side note: You could implement custom binding handles as described in <a href="https://docs.microsoft.com/en-us/windows/win32/rpc/primitive-and-custom-binding-handles">here</a>, but we ignore this for this post, as this is rather uncommon and you’re good with the default types.</p>
<p><strong>Implicit binding handles</strong> allow your client to connect to and communicate with a specific RPC server (specified by the UUID in the IDL file). The downside is implicit bindings are not thread safe, multi-threaded applications should therefore use explicit bindings. Implicit binding handles are defined in the IDL file as shown in the sample IDL code above or in my <a href="https://github.com/csandker/InterProcessCommunication-Samples/blob/master/RPC/CPP-RPC-Client-Server/RPC-Interface1/Interface1-Implicit.idl">Sample Implicit Interface</a>.<br />
<strong>Explicit binding handles</strong> allow your client to connect to and communicate with multiple RPC servers. Explicit binding handles are recommended to use due to being thread safe and allow for multiple connections. An example of an explicit binding handle definition can be found in my code <a href="https://github.com/csandker/InterProcessCommunication-Samples/blob/master/RPC/CPP-RPC-Client-Server/RPC-Interface1/Interface1-Explicit.idl">here</a>.<br />
<strong>Automatic binding</strong> is a solution in between for the lazy developer, who doesn’t want to fiddle around with binding handles and let the RPC runtime figure out what is needed. My recommendation would be to use explicit handles just to be aware of what you’re doing.</p>
<p>Why do i need binding handles in the first place you might ask at this point.<br />
Imagine a binding handle as a representation of your communication channel between client and server, just like the cord in a <a href="https://www.google.com/search?q=tin+can+phone&oq=tin+can+phone&sclient=img">can phone</a> (i wonder how many people know these ‘devices’…). Given that you have a representation of the communication chanel (‘the cord’) you can add attributes to this communication channel, like painting your cord to make it more unique.<br />
Just like that binding handles allow you for example to secure the connection between your client and server (because you got something that you can add security to) and therefore form what Microsoft terms <strong>“authenticated” bindings</strong>.</p>
<h3 id="anonymous--authenticated-bindings">Anonymous & Authenticated Bindings</h3>
<p>Let’s say you’ve got a plain and simple RPC server running, now a client connects to your server. If you didn’t specify anything expect the bare minimum (which i will list shortly), this connection between client and server is referred to as <strong>anonymous</strong> or <strong>unauthenticated</strong> binding, due to the fact that your server got no clue who connected to it.<br />
To avoid any client from connecting and to level up the security of your server there are three gears you can turn:</p>
<ul>
<li>You can set registration flags when registering your server interface; And/Or</li>
<li>You can set a Security callback with a custom routine to check whether a requesting client should be allowed or denied; And/Or</li>
<li>You can set authentication information associated with your binding handle to specify a security service provider and an SPN to represent your RPC server.</li>
</ul>
<p>Let’s look at those three gears step-by-step.<br /></p>
<h4 id="registration-flags">Registration Flags</h4>
<p>First of all when you create your server you need to register your interface, for example with a call to <a href="https://docs.microsoft.com/en-us/windows/win32/api/rpcdce/nf-rpcdce-rpcserverregisterif2">RpcServerRegisterIf2</a> - I’ll show you where this call comes into play in section <a href="#rpc-communication-flow">RPC Communication Flow</a>. As a fourth parameter to <em>RpcServerRegisterIf2</em> you can specify <a href="https://docs.microsoft.com/en-us/windows/win32/rpc/interface-registration-flags">Interface Registration Flags</a>, such as RPC_IF_ALLOW_LOCAL_ONLY to only allow local connections.<br />
<em>Side note: Read this as RPC_<strong>I</strong>nter<strong>F</strong>ace_ALLOW_LOCAL_ONLY</em></p>
<p>A sample call could look like this:</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">RPC_STATUS</span> <span class="n">rpcStatus</span> <span class="o">=</span> <span class="n">RpcServerRegisterIf2</span><span class="p">(</span>
<span class="n">Example1_v1_0_s_ifspec</span><span class="p">,</span> <span class="c1">// Interface to register.</span>
<span class="nb">NULL</span><span class="p">,</span> <span class="c1">// NULL type UUID</span>
<span class="nb">NULL</span><span class="p">,</span> <span class="c1">// Use the MIDL generated entry-point vector.</span>
<span class="n">RPC_IF_ALLOW_LOCAL_ONLY</span><span class="p">,</span> <span class="c1">// Only allow local connections</span>
<span class="n">RPC_C_LISTEN_MAX_CALLS_DEFAULT</span><span class="p">,</span> <span class="c1">// Use default number of concurrent calls.</span>
<span class="p">(</span><span class="kt">unsigned</span><span class="p">)</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="c1">// Infinite max size of incoming data blocks.</span>
<span class="nb">NULL</span> <span class="c1">// No security callback.</span>
<span class="p">);</span>
</code></pre></div></div>
<h4 id="security-callbacks">Security Callbacks</h4>
<p>Next on the list is the security callback, which you could set as the last parameter of the above call. An always-allow callback could look like this:</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Naive security callback.</span>
<span class="n">RPC_STATUS</span> <span class="n">CALLBACK</span> <span class="nf">SecurityCallback</span><span class="p">(</span><span class="n">RPC_IF_HANDLE</span> <span class="n">hInterface</span><span class="p">,</span> <span class="kt">void</span><span class="o">*</span> <span class="n">pBindingHandle</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">return</span> <span class="n">RPC_S_OK</span><span class="p">;</span> <span class="c1">// Always allow anyone.</span>
<span class="p">}</span>
</code></pre></div></div>
<p>To include this Security callback simply set the last parameter of the <em>RpcServerRegisterIf2</em> function to the name of your security callback function, which in this case is just named “SecurityCallback”, as shown below:</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">RPC_STATUS</span> <span class="n">rpcStatus</span> <span class="o">=</span> <span class="n">RpcServerRegisterIf2</span><span class="p">(</span>
<span class="n">Example1_v1_0_s_ifspec</span><span class="p">,</span> <span class="c1">// Interface to register.</span>
<span class="nb">NULL</span><span class="p">,</span> <span class="c1">// Use the MIDL generated entry-point vector.</span>
<span class="nb">NULL</span><span class="p">,</span> <span class="c1">// Use the MIDL generated entry-point vector.</span>
<span class="n">RPC_IF_ALLOW_LOCAL_ONLY</span><span class="p">,</span> <span class="c1">// Only allow local connections</span>
<span class="n">RPC_C_LISTEN_MAX_CALLS_DEFAULT</span><span class="p">,</span> <span class="c1">// Use default number of concurrent calls.</span>
<span class="p">(</span><span class="kt">unsigned</span><span class="p">)</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="c1">// Infinite max size of incoming data blocks.</span>
<span class="n">SecurityCallback</span> <span class="c1">// No security callback.</span>
<span class="p">);</span>
</code></pre></div></div>
<p>This callback function can be implemented in any way you like, you could for example allow/deny connections based on IPs.</p>
<h4 id="authenticated-bindings">Authenticated Bindings</h4>
<p>Alright we’re getting closer to the end of the RPC terminology and background section… Stay with me while we dig into the last concepts.<br />
As I can feel the pain to follow up for people who are new to all these terms, let’s take a moment to recap:</p>
<p>Okay so far you should know that you can create implicit and explicit interfaces and use a few Windows API calls to setup your RPC server. In the previous section I’ve added that once you register your server you can set registration flags and (if you want to) also a callback function to secure you server and filter the clients who can access your server. The last piece in the puzzle is now an extra Windows API that allows the server and client to authenticate your binding (remember that one of the benefits of having a binding handle is that you can authenticate your binding, like ‘painting your cord for your can phone’).<br />
… But why would/should you do that?<br />
<strong>Authenticated Bindings in combination with the right registration flag (RPC_IF_ALLOW_SECURE_ONLY) enables your RPC Server to ensure that only authenticated users can connect; And - in case the client allows it - enables the server to figure out who connected to it by impersonating the client</strong>.<br /></p>
<p><em>To backup what you learned before:</em> You could as well use the SecurityCallback to deny any anonymous client from connecting, but you would need to implement the filter mechanism on your own, based on attributes you control.<br />
Example: You wouldn’t be able to determine if the client is for example a valid domain user, because you don’t have any access to these account information.</p>
<p>Okay so how do you specify an authenticated binding?<br />
You <u>can</u> authenticate your binding on the server and on the client side. On the server side you want to implement this to ensure a secured connection and on the client side you might need to have this in order to be able to connect to your server (as we’ll see shortly in the <a href="#access-matrix">Access Matrix</a>)</p>
<p><strong>Authenticating the binding on the Server side:</strong> [Taken from my example code <a href="https://github.com/csandker/InterProcessCommunication-Samples/blob/master/RPC/CPP-RPC-Client-Server/RPC-Server1-Explicit-SecurityCallback-Auth/RPC-Server-Explicit-SecurityCallback-Auth.cpp#L179">here</a>]</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">RPC_STATUS</span> <span class="n">rpcStatus</span> <span class="o">=</span> <span class="n">RpcServerRegisterAuthInfo</span><span class="p">(</span>
<span class="n">pszSpn</span><span class="p">,</span> <span class="c1">// Server principal name</span>
<span class="n">RPC_C_AUTHN_WINNT</span><span class="p">,</span> <span class="c1">// using NTLM as authentication service provider</span>
<span class="nb">NULL</span><span class="p">,</span> <span class="c1">// Use default key function, which is ignored for NTLM SSP</span>
<span class="nb">NULL</span> <span class="c1">// No arg for key function</span>
<span class="p">);</span>
</code></pre></div></div>
<p><strong>Authenticating the binding on the client side:</strong> [Taken from my example code <a href="https://github.com/csandker/InterProcessCommunication-Samples/blob/master/RPC/CPP-RPC-Client-Server/RPC-Client1-Explicit-Auth-QOS/RPC-Client1-Explicit-Auth-QOS.cpp#L84">here</a>]</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">RPC_STATUS</span> <span class="n">status</span> <span class="o">=</span> <span class="n">RpcBindingSetAuthInfoEx</span><span class="p">(</span>
<span class="n">hExplicitBinding</span><span class="p">,</span> <span class="c1">// the client's binding handle</span>
<span class="n">pszHostSPN</span><span class="p">,</span> <span class="c1">// the server's service principale name (SPN)</span>
<span class="n">RPC_C_AUTHN_LEVEL_PKT</span><span class="p">,</span> <span class="c1">// authentication level PKT</span>
<span class="n">RPC_C_AUTHN_WINNT</span><span class="p">,</span> <span class="c1">// using NTLM as authentication service provider</span>
<span class="nb">NULL</span><span class="p">,</span> <span class="c1">// use current thread credentials</span>
<span class="n">RPC_C_AUTHZ_NAME</span><span class="p">,</span> <span class="c1">// authorization based on the provided SPN</span>
<span class="o">&</span><span class="n">secQos</span> <span class="c1">// Quality of Service structure</span>
<span class="p">);</span>
</code></pre></div></div>
<p>The interesting bit on the client side is that you can set a <a href="https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-security_quality_of_service"><strong>Quality of Service (QOS)</strong></a> structure with your authenticated binding handle. This QOS structure can for example be used on the client side to determine the <strong>Impersonation Level</strong> (for background information check out my <a href="https://csandker.io/2021/01/10/Offensive-Windows-IPC-1-NamedPipes.html#impersonation">previous IPC post</a> ), which we’ll later cover in section <a href="#client-impersonation">Client Impersonation</a>.</p>
<p><strong>Important to note</strong>:<br />
<strong>Setting an authenticated binding on the server side, does not enforce an authentication on the client side.</strong><br />
If for example no flags are set on the server side or only the <em>RPC_IF_ALLOW_CALLBACKS_WITH_NO_AUTH</em> is set, unauthenticated clients can still connect to the RPC server.<br />
Setting the <em>RPC_IF_ALLOW_SECURE_ONLY</em> flag however prevents unauthenticated client bindings, because the client can’t set an authentication level (which is what is checked with this flag) without creating an authenticated binding.</p>
<h3 id="well-known-vs-dynamic-endpoints">Well-known vs Dynamic Endpoints</h3>
<p>Last but not least we have to clarify one last important aspect of RPC communication: Well-known vs Dynamic endpoints.<br />
I’ll try to make this one short as it’s also quite easy to understand…</p>
<p>When you spin up your RPC server, the server registers an interface (as we’ve seen already in the code sample above with <a href="https://docs.microsoft.com/en-us/windows/win32/api/rpcdce/nf-rpcdce-rpcserverregisterif2">RpcServerRegisterIf2</a>) and it also needs to define on which protocol sequence (e.g. ‘ncacn_ip_tcp’, ‘ncacn_np’, …) it wants to listen to.<br />
Now the protocol sequence string that you specify in your server is not quite enough to open a RPC port connection. Imagine you specify ‘ncacn_ip_tcp’ as your protocol sequence, meaning you instruct your server to open up an RPC connection that accepts connections via TCP/IP… but … on which TCP port should the server actually open up a connection?<br />
Similar to <em>ncacn_ip_tcp</em> other protocol sequences also need a little more information about <em>where</em> to open a connection object:</p>
<ul>
<li>ncacn_ip_tcp needs a TCP port number, e.g. 9999</li>
<li>ncacn_np needs a Named Pipe name, e .g. “\pipe\FRPC-NP”</li>
<li>ncalrpc needs an ALPC port name, e.g. “\RPC Control\FRPC-LRPC”</li>
</ul>
<p>Let’s assume for a moment you specified <em>ncacn_np</em> as the protocol sequence and chose the Named Pipe name to be “\pipe\FRPC-NP”.<br />
Your RPC server will happily spin up and is now waiting for clients to connect. The client on the other hand needs to know where it should connect to. You tell your client the server’s name, specify the protocol sequence to be <em>ncacn_np</em> and set the Named Pipe name to the same name you defined in your server (“\pipe\FRPC-NP”). The client connects successfully and just like that you’ve built a RPC client and server based on a Well-known endpoint… which in this case is: “\pipe\FRPC-NP”.<br />
Using <strong>Well-known</strong> RPC endpoints just means you know all the binding information (protocol sequence and endpoint-address) upfront and could - if you want to - also hardcode those information in your client and server. Using Well-known endpoints is the easiest way to build up your first RPC client/server connection.</p>
<p>What are <strong>Dynamic endpoints</strong> then and why should one use them?<br />
In the example above we choose <em>ncacn_np</em> and just picked any arbitrary Named Pipe name to open our server and that worked just fine, because we knew (well at least we hoped) the Named Pipe that we’ve opened up with this name didn’t already exist on the server side, because we just made a name up. If we now choose <em>ncacn_ip_tcp</em> to be the protocol sequence how do we know which TCP port is still available for us? Well we could just specify that our program needs port 9999 to be functional and leave it to the Admins to ensure that this port is unused, but we could also ask Windows to assign us a port that is free. And that is what <strong>Dynamic endpoints</strong> are. Easy … case closed, let’s go for beers<br />
Wait a minute: If we get assigned a port dynamically, how does the client know where to connect to ?!…<br />
And that is the other thing with Dynamic endpoints: If you chose dynamic endpoints you need someone to tell your client what port you got and that someone is the <strong>RPC Endpoint Mapper</strong> service (started and running by default on your Windows system). If your server is using Dynamic Endpoints
it will need to call the RPC Endpoint Mapper to tell it to register its Interface and functions (specified in the IDL file). Once the client attempts to create the binding it will query the server’s RPC Endpoint Mapper for matching interfaces and the Endpoint Mapper will fill in the missing information (e.g. the TCP port) to create the binding.</p>
<p>The main advantage of <strong>Dynamic Endpoints</strong> is to automatically find an available endpoint-address when the endpoint-address space is limited, as it is the case with TCP ports. Named Pipes and ALPC based connections can also safely be done with <strong>Well-known</strong> endpoints, because the address space (aka. the arbitrary pipe or port name that you’ve chosen) is large enough to avoid collisions.</p>
<p>We’ll wrap this up with code snippets from the server side to nail our understanding of Well-known and Dynamic endpoints.</p>
<p><strong>Well-Known Endpoint Implementation</strong></p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">RPC_STATUS</span> <span class="n">rpcStatus</span><span class="p">;</span>
<span class="c1">// Create Binding Information</span>
<span class="n">rpcStatus</span> <span class="o">=</span> <span class="n">RpcServerUseProtseqEp</span><span class="p">(</span>
<span class="p">(</span><span class="n">RPC_WSTR</span><span class="p">)</span><span class="s">L"ncacn_np"</span><span class="p">,</span> <span class="c1">// using Named Pipes here</span>
<span class="n">RPC_C_PROTSEQ_MAX_REQS_DEFAULT</span><span class="p">,</span> <span class="c1">// Ignored for Named Pipes (only used for ncacn_ip_tcp, but set this anyway)</span>
<span class="p">(</span><span class="n">RPC_WSTR</span><span class="p">)</span><span class="s">L"</span><span class="se">\\</span><span class="s">pipe</span><span class="se">\\</span><span class="s">FRPC-NP"</span><span class="p">,</span> <span class="c1">// example Named Pipe name</span>
<span class="nb">NULL</span> <span class="c1">// No Secuirty Descriptor</span>
<span class="p">);</span>
<span class="c1">// Register Interface</span>
<span class="n">rpcStatus</span> <span class="o">=</span> <span class="n">RpcServerRegisterIf2</span><span class="p">(...)</span> <span class="c1">// As shown in the examples above</span>
<span class="c1">// OPTIONAL: Register Authentication Information</span>
<span class="n">rpcStatus</span> <span class="o">=</span> <span class="n">RpcServerRegisterAuthInfo</span><span class="p">(...)</span> <span class="c1">// As shown in the example above</span>
<span class="c1">// Listen for incoming client connections</span>
<span class="n">rpcStatus</span> <span class="o">=</span> <span class="n">RpcServerListen</span><span class="p">(</span>
<span class="mi">1</span><span class="p">,</span> <span class="c1">// Recommended minimum number of threads.</span>
<span class="n">RPC_C_LISTEN_MAX_CALLS_DEFAULT</span><span class="p">,</span> <span class="c1">// Recommended maximum number of threads.</span>
<span class="n">FALSE</span> <span class="c1">// Start listening now.</span>
<span class="p">);</span>
</code></pre></div></div>
<p><strong>Dynamic Endpoint Implementation</strong></p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">RPC_STATUS</span> <span class="n">rpcStatus</span><span class="p">;</span>
<span class="n">RPC_BINDING_VECTOR</span><span class="o">*</span> <span class="n">pbindingVector</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="c1">// Create Binding Information</span>
<span class="n">rpcStatus</span> <span class="o">=</span> <span class="n">RpcServerUseProtseq</span><span class="p">(</span>
<span class="p">(</span><span class="n">RPC_WSTR</span><span class="p">)</span><span class="s">L"ncacn_ip_tcp"</span><span class="p">,</span> <span class="c1">// using Named Pipes here</span>
<span class="n">RPC_C_PROTSEQ_MAX_REQS_DEFAULT</span><span class="p">,</span> <span class="c1">// Backlog queue length for the ncacn_ip_tcp protocol sequenc</span>
<span class="nb">NULL</span> <span class="c1">// No Secuirty Descriptor</span>
<span class="p">);</span>
<span class="c1">// Register Interface</span>
<span class="n">rpcStatus</span> <span class="o">=</span> <span class="n">RpcServerRegisterIf2</span><span class="p">(...)</span> <span class="c1">// As shown in the examples above</span>
<span class="c1">// OPTIONAL: Register Authentication Information</span>
<span class="n">rpcStatus</span> <span class="o">=</span> <span class="n">RpcServerRegisterAuthInfo</span><span class="p">(...)</span> <span class="c1">// As shown in the example above</span>
<span class="c1">// Get Binding vectors (dynamically assigend)</span>
<span class="n">rpcStatus</span> <span class="o">=</span> <span class="n">RpcServerInqBindings</span><span class="p">(</span><span class="o">&</span><span class="n">pbindingVector</span><span class="p">);</span>
<span class="c1">// Register with RPC Endpoint Mapper</span>
<span class="n">rpcStatus</span> <span class="o">=</span> <span class="n">RpcEpRegister</span><span class="p">(</span>
<span class="n">Example1_v1_0_s_ifspec</span><span class="p">,</span> <span class="c1">// your interface as defined via IDL</span>
<span class="n">pbindingVector</span><span class="p">,</span> <span class="c1">// your dynamic binding vectors</span>
<span class="mi">0</span><span class="p">,</span> <span class="c1">// We don't want to register the vectors with UUIDs</span>
<span class="p">(</span><span class="n">RPC_WSTR</span><span class="p">)</span><span class="s">L"MyDyamicEndpointServer"</span> <span class="c1">// Annotation used for information purposes only, max 64 characters </span>
<span class="p">);</span>
<span class="c1">// Listen for incoming client connections</span>
<span class="n">rpcStatus</span> <span class="o">=</span> <span class="n">RpcServerListen</span><span class="p">(</span>
<span class="mi">1</span><span class="p">,</span> <span class="c1">// Recommended minimum number of threads.</span>
<span class="n">RPC_C_LISTEN_MAX_CALLS_DEFAULT</span><span class="p">,</span> <span class="c1">// Recommended maximum number of threads.</span>
<span class="n">FALSE</span> <span class="c1">// Start listening now.</span>
<span class="p">);</span>
</code></pre></div></div>
<p>Note: If you’re using Well-known endpoints you could as well register your RPC server with your local RPC Endpoint Mapper by calling <a href="https://docs.microsoft.com/en-us/windows/win32/api/rpcdce/nf-rpcdce-rpcserverinqbindings">RpcServerInqBindings</a> & <a href="https://docs.microsoft.com/en-us/windows/win32/api/rpcdce/nf-rpcdce-rpcepregister">RpcEpRegister</a> if you want to. You don’t need to do that for your client to be able to connect, but you could.</p>
<p>If you want to read more on this, the Microsoft documentation on this topic can be found here:<br />
<a href="https://docs.microsoft.com/en-us/windows/win32/rpc/specifying-endpoints">https://docs.microsoft.com/en-us/windows/win32/rpc/specifying-endpoints</a></p>
<h3 id="rpc-communication-flow">RPC Communication Flow</h3>
<p>To wrap up all of the above, the communication flow can be summarized as follows:</p>
<ol>
<li><strong>Server</strong> registers Interface(s), e.g. using <a href="https://docs.microsoft.com/en-us/windows/win32/api/rpcdce/nf-rpcdce-rpcserverregisterif2">RpcServerRegisterIf2</a></li>
<li><strong>Server</strong> creates Binding Information using <a href="https://docs.microsoft.com/en-us/windows/win32/api/rpcdce/nf-rpcdce-rpcserveruseprotseq">RpcServerUseProtseq</a> & <a href="https://docs.microsoft.com/en-us/windows/win32/api/rpcdce/nf-rpcdce-rpcserverinqbindings">RpcServerInqBindings</a> (<em>RpcServerInqBindings</em> is optional for <a href="#well-known-vs-dynamic-endpoints">Well-known Endpoints</a>)</li>
<li><strong>Server</strong> registers Endpoints using <a href="https://docs.microsoft.com/en-us/windows/win32/api/rpcdce/nf-rpcdce-rpcepregister">RpcEpRegister</a> (optional for <a href="#well-known-vs-dynamic-endpoints">Well-known Endpoints</a>)</li>
<li><strong>Server</strong> <u>can</u> register Authentication Information using <a href="https://docs.microsoft.com/en-us/windows/win32/api/rpcdce/nf-rpcdce-rpcserverregisterauthinfo">RpcServerRegisterAuthInfo</a> (optional)</li>
<li><strong>Server</strong> listens for client connection(s) using <a href="https://docs.microsoft.com/en-us/windows/win32/api/rpcdce/nf-rpcdce-rpcserverlisten">RpcServerListen</a></li>
<li><strong>Client</strong> creates a Binding Handle, using <a href="https://docs.microsoft.com/en-us/windows/win32/api/rpcdce/nf-rpcdce-rpcstringbindingcompose">RpcStringBindingCompose</a> & <a href="https://docs.microsoft.com/en-us/windows/win32/api/rpcdce/nf-rpcdce-rpcbindingfromstringbinding">RpcBindingFromStringBinding</a></li>
<li><strong>Client</strong> RPC runtime library finds the server process by querying the Endpoint Mapper on the server host system (only necessary for <a href="#well-known-vs-dynamic-endpoints">Dynamic Endpoints</a>)</li>
<li><strong>Client</strong> <u>can</u> authenticate binding handle using <a href="https://docs.microsoft.com/en-us/windows/win32/api/rpcdce/nf-rpcdce-rpcbindingsetauthinfo">RpcBindingSetAuthInfo</a> (optional)</li>
<li><strong>Client</strong> makes an RPC call by calling one of the functions defined in the used interface</li>
<li><strong>Client</strong> RPC runtime library marshals the arguments in an <a href="https://docs.microsoft.com/en-us/windows/win32/rpc/rpc-ndr-engine">NDR</a> format with the help of the NDR runtime and send them to the server,</li>
<li>The <strong>Server</strong>’s RPC run time library gives the marshaled arguments to the stub, which unmarshals them, and then passes them to the server routines.</li>
<li>When the <strong>Server</strong> routines return, the stub picks up the [out] and [in, out] parameters (defined in the interface IDL file) and the return value, marshals them, and sends the marshaled data to the Server’s RPC run time library, which transfers them back to the client.</li>
</ol>
<p><img src="/public/img/2021-02-21-Offensive-Windows-IPC-2-RPC/RPC_Communication_Flow.png" alt="RPC Access Matrix" /></p>
<h3 id="sample-implementation">Sample Implementation</h3>
<p>As mentioned in the beginning the examples above are taken from my sample implementation, publicly available at: <br />
<a href="https://github.com/csandker/InterProcessCommunication-Samples/tree/master/RPC/CPP-RPC-Client-Server">https://github.com/csandker/InterProcessCommunication-Samples/tree/master/RPC/CPP-RPC-Client-Server</a>.<br />
In this repo you will find the following sample implementations:</p>
<ul>
<li>Basic unauthenticated Server supporting unauthenticated Implicit Bindings</li>
<li>Basic unauthenticated Client supporting unauthenticated Implicit Bindings</li>
<li>Basic Server supporting unauthenticated Explicit Bindings</li>
<li>Basic Server supporting authenticated Explicit Bindings</li>
<li>Basic Client supporting authenticated Explicit Bindings without QOS</li>
<li>Basic Client supporting authenticated Explicit Bindings with QOS</li>
</ul>
<p>An example how these PoCs look can be seen below:</p>
<p><img src="/public/img/2021-02-21-Offensive-Windows-IPC-2-RPC/RPC_ClientServer_Messages.png" alt="RPC Client Server Messages" /></p>
<h2 id="access-matrix">Access Matrix</h2>
<p>Alright if you understood all of the terminology above, here’s the access matrix that visualizes which client can connect to which server.<br />
<em>Note: You can only connect an implicit clients to implicit servers, and explicit clients to explicit servers. Otherwise you get an Error 1717 (RPC_S_UNKNOWN_IF)</em></p>
<p><img src="/public/img/2021-02-21-Offensive-Windows-IPC-2-RPC/RPC_AccessMatrix.png" alt="RPC Access Matrix" /></p>
<h2 id="attack-surface">Attack Surface</h2>
<p>Finally… after all that talk about RPC internals, let’s talk about RPC’s attack surface.<br />
Obviously there could be bugs and 0-days anywhere in the RPC communication chain, which always comes down to a case-by-case analysis to understand its exploit potentials, but there is also some exploitation potential of general RPC design concepts, which I’ll highlight below.<br />
<em>Side note: If you are aware of interesting RPC CVEs, ping me at <span class="icon-span"><i class="fab fa-twitter">/</i><a href="https://twitter.com/0xcsandker">0xcsandker</a></span></em></p>
<h3 id="finding-interesting-targets">Finding Interesting Targets</h3>
<p>Okay so before we can think what offensive games we can play with RPC, we need to find suitable targets first.<br />
Let’s dive into how we can find RPC Servers and clients on your systems.</p>
<h4 id="rpc-servers">RPC Servers</h4>
<p>To recap a server is built by specify the required information (protocol sequence & endpoint-address) and calling Windows APIs to built the necessary internal objects and start the server. With that in mind the easiest way to find RPC servers on your local system is by looking for programs that import those RPC Windows APIs.<br />
One easy way to do that is by using the <a href="https://docs.microsoft.com/en-us/cpp/build/reference/dumpbin-reference?view=msvc-160">DumpBin</a> utility that nowadays ships with Visual Studio.</p>
<p>A sample Powershell snippet searching through <code class="language-plaintext highlighter-rouge">C:\Windows\System32\</code> on a recent Windows10 can be found below:</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Get-ChildItem</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="s2">"C:\Windows\System32\"</span><span class="w"> </span><span class="nt">-Filter</span><span class="w"> </span><span class="s2">"*.exe"</span><span class="w"> </span><span class="nt">-Recurse</span><span class="w"> </span><span class="nt">-ErrorAction</span><span class="w"> </span><span class="nx">SilentlyContinue</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="o">%</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nv">$out</span><span class="o">=</span><span class="err">$</span><span class="p">(</span><span class="n">C:\</span><span class="s2">"Program Files (x86)"</span><span class="nx">\</span><span class="s2">"Microsoft Visual Studio 14.0"</span><span class="nx">\VC\bin\dumpbin.exe</span><span class="w"> </span><span class="nx">/IMPORTS:rpcrt4.dll</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">VersionInfo</span><span class="o">.</span><span class="nf">FileName</span><span class="p">);</span><span class="w"> </span><span class="kr">If</span><span class="p">(</span><span class="nv">$out</span><span class="w"> </span><span class="o">-like</span><span class="w"> </span><span class="s2">"*RpcServerListen*"</span><span class="p">){</span><span class="w"> </span><span class="n">Write-Host</span><span class="w"> </span><span class="s2">"[+] Exe starting RPC Server: </span><span class="si">$(</span><span class="bp">$_</span><span class="o">.</span><span class="nf">VersionInfo</span><span class="o">.</span><span class="nf">FileName</span><span class="si">)</span><span class="s2">"</span><span class="p">;</span><span class="w"> </span><span class="n">Write-Output</span><span class="w"> </span><span class="s2">"[+] </span><span class="si">$(</span><span class="bp">$_</span><span class="o">.</span><span class="nf">VersionInfo</span><span class="o">.</span><span class="nf">FileName</span><span class="si">)</span><span class="se">`n`n</span><span class="s2"> </span><span class="si">$(</span><span class="nv">$out</span><span class="o">|%</span><span class="p">{</span><span class="s2">"</span><span class="bp">$_</span><span class="se">`n</span><span class="s2">"</span><span class="p">}</span><span class="si">)</span><span class="s2">"</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Out-File</span><span class="w"> </span><span class="nt">-FilePath</span><span class="w"> </span><span class="nx">EXEs_RpcServerListen.txt</span><span class="w"> </span><span class="nt">-Append</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>This snippet prints the names of the executables to console and the entire DumpBin output to the file <em>EXEs_RpcServerListen.txt</em> (so that you can review what DumpBin actually gives you).</p>
<p><img src="/public/img/2021-02-21-Offensive-Windows-IPC-2-RPC/RPC_List_Servers_By_Export.png" alt="RPC Servers By Export" /></p>
<p>Another way to find interesting RPC servers is by querying the RPC Endpoint Mapper, either locally or on any remote system.<br />
Microsoft has a test utility called <a href="https://www.microsoft.com/en-us/download/details.aspx?id=17148">PortQry</a> to do that (there is also a GUI version of that tool available) that you can use like this: <code class="language-plaintext highlighter-rouge">C:\PortQryV2\PortQry.exe -n <HostName> -e 135</code></p>
<p><img src="/public/img/2021-02-21-Offensive-Windows-IPC-2-RPC/PortQryv2.png" alt="PortQry v2" /></p>
<p>This tool gives you some information about remote RPC interfaces that the Endpoint Mapper knows about (remember that <a href="#well-known-vs-dynamic-endpoints">Well-known Endpoints</a> do not have to inform the Endpoint Mapper about their interfaces).</p>
<p>Another option is to query the Endpoint Manager directly by calling <a href="https://docs.microsoft.com/en-us/windows/win32/api/rpcdce/nf-rpcdce-rpcmgmtepeltinqbegin">RpcMgmtEpEltInqBegin</a> and iterating over the interfaces via <a href="https://docs.microsoft.com/en-us/windows/win32/api/rpcdce/nf-rpcdce-rpcmgmtepeltinqnext">RpcMgmtEpEltInqNext</a>. A sample implementation, named <strong>RPCDump</strong>, of this approach was included in Chris McNab’s amazing book ‘<em>Network Security Assessment</em>’, O’Reilly published the tool written in C <a href="https://resources.oreilly.com/examples/9780596510305/blob/master/tools/rpctools/rpcdump/rpcdump.c">here</a> (according to the comment annotation credits for this code should go to Todd Sabin).<br />
I have ported this cool tool to VC++ and made some slight usability changes. I’ve published my fork at <a href="https://github.com/csandker/RPCDump">https://github.com/csandker/RPCDump</a>.</p>
<p><img src="/public/img/2021-02-21-Offensive-Windows-IPC-2-RPC/RPC_Dump.png" alt="RPC Dump" /></p>
<p>As shown this tool also list the interfaces of the found RPC endpoints, along with some other information. I won’t go into the details of all these fields, but if you’re interested check out <a href="https://github.com/csandker/RPCDump">the code</a> and read along the Windows API documentation. The stats for example are retrieved by a call to <a href="https://docs.microsoft.com/en-us/windows/win32/api/rpcdce/nf-rpcdce-rpcmgmtinqstats">RpcMgmtInqStats</a>, where the values returned are referenced in the <a href="https://docs.microsoft.com/en-us/windows/win32/api/rpcdce/nf-rpcdce-rpcmgmtinqstats#remarks">Remarks</a> section.</p>
<p>Once again remember that there are only the RPC interfaces that are registered with the target’s Endpoint Mapper.</p>
<h4 id="rpc-clients">RPC Clients</h4>
<p>Finding clients that connect to remote or local RPC servers might also be an interesting target.<br />
There is no single authority that is aware of which RPC clients are currently running, therefore you’re down to two options for finding clients:</p>
<ul>
<li>Finding executables/Processes that use client RPC APIs; Or</li>
<li>Caught clients in the act</li>
</ul>
<p>Finding local executables that import client RPC API is analogous to what we already did to find servers with <a href="https://docs.microsoft.com/en-us/cpp/build/reference/dumpbin-reference?view=msvc-160">DumpBin</a>. A good Windows API to look for is <a href="https://docs.microsoft.com/en-us/windows/win32/api/rpcdce/nf-rpcdce-rpcstringbindingcompose">RpcStringBindingCompose</a>:</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Get-ChildItem</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="s2">"C:\Windows\System32\"</span><span class="w"> </span><span class="nt">-Filter</span><span class="w"> </span><span class="s2">"*.exe"</span><span class="w"> </span><span class="nt">-Recurse</span><span class="w"> </span><span class="nt">-ErrorAction</span><span class="w"> </span><span class="nx">SilentlyContinue</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="o">%</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nv">$out</span><span class="o">=</span><span class="err">$</span><span class="p">(</span><span class="n">C:\</span><span class="s2">"Program Files (x86)"</span><span class="nx">\</span><span class="s2">"Microsoft Visual Studio 14.0"</span><span class="nx">\VC\bin\dumpbin.exe</span><span class="w"> </span><span class="nx">/IMPORTS:rpcrt4.dll</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">VersionInfo</span><span class="o">.</span><span class="nf">FileName</span><span class="p">);</span><span class="w"> </span><span class="kr">If</span><span class="p">(</span><span class="nv">$out</span><span class="w"> </span><span class="o">-like</span><span class="w"> </span><span class="s2">"*RpcStringBindingCompose*"</span><span class="p">){</span><span class="w"> </span><span class="n">Write-Host</span><span class="w"> </span><span class="s2">"[+] Exe creates RPC Binding (potential RPC Client) : </span><span class="si">$(</span><span class="bp">$_</span><span class="o">.</span><span class="nf">VersionInfo</span><span class="o">.</span><span class="nf">FileName</span><span class="si">)</span><span class="s2">"</span><span class="p">;</span><span class="w"> </span><span class="n">Write-Output</span><span class="w"> </span><span class="s2">"[+] </span><span class="si">$(</span><span class="bp">$_</span><span class="o">.</span><span class="nf">VersionInfo</span><span class="o">.</span><span class="nf">FileName</span><span class="si">)</span><span class="se">`n`n</span><span class="s2"> </span><span class="si">$(</span><span class="nv">$out</span><span class="o">|%</span><span class="p">{</span><span class="s2">"</span><span class="bp">$_</span><span class="se">`n</span><span class="s2">"</span><span class="p">}</span><span class="si">)</span><span class="s2">"</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Out-File</span><span class="w"> </span><span class="nt">-FilePath</span><span class="w"> </span><span class="nx">EXEs_RpcClients.txt</span><span class="w"> </span><span class="nt">-Append</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p><img src="/public/img/2021-02-21-Offensive-Windows-IPC-2-RPC/RPC_List_Clients_By_Export.png" alt="RPC Clients By Export" /></p>
<p>The other option to find RPC clients is by spotting them while they’re connection to their target. One example of how clients can be spotted is by inspecting the traffic that is sent over the wire between two systems. Wireshark has a ‘DCERPC’ filter that can be used to spot connections.<br />
An example of a client connecting to a server is shown below:</p>
<p><img src="/public/img/2021-02-21-Offensive-Windows-IPC-2-RPC/RPC_Client_DCERPC_Connection.png" alt="RPC Clients By Export" /></p>
<p>The bind request is one of the things we can look for to identify clients. In the select package we can see a client trying to bind to a server interface with the UUID of “d6b1ad2b-b550-4729-b6c2-1651f58480c3”.</p>
<h3 id="unauthorized-access">Unauthorized Access</h3>
<p>Once you identified an RPC server that exposes interesting functionality that could be useful to your attack chain the most obvious thing to check is if you can access the server unauthorized.<br />
You could either implement your own client, e.g. based on the my <a href="#sample-implementation">Sample Implementation</a>, or refer to the <a href="#access-matrix">Access Matrix</a> to check if your client can connect to the server.</p>
<p>If you already got heads deep into reverse engineering the RPC server and found that the server sets authentication information by calling <a href="https://docs.microsoft.com/en-us/windows/win32/api/rpcdce/nf-rpcdce-rpcserverregisterauthinfo">RpcServerRegisterAuthInfo</a> with its SPN and a specified Service Provider, be reminded that <strong>an authenticated server binding does not enforce the client to use an authenticated binding</strong>. In other words: Just because the server sets authentication information does not mean the client needs to connect through an authenticated binding. Moreover when connecting to a server that sets authentication information be aware that <strong>client calls with invalid credentials will not be dispatched by the run time library (rpcrt4.dll), however, client calls with no credentials will be dispatched</strong>. Or to put it with Microsoft words:</p>
<blockquote>
<p>Remember that, by default, security is optional<br />
<em>Source: <a href="https://docs.microsoft.com/en-us/windows/win32/api/rpcdce/nf-rpcdce-rpcserverregisterifex">https://docs.microsoft.com/en-us/windows/win32/api/rpcdce/nf-rpcdce-rpcserverregisterifex</a></em></p>
</blockquote>
<p>Once you are connected to a server the question of “what to do next?” arises…<br />
Well you’re then in a position to call interface functions, the bad news is: You need to identify the function names and parameters first, which comes down to reverse engineering your target server.<br />
If you’re lucking and you’re not looking at a pure RPC server, but a COM server (COM, especially DCOM, uses RPC under the hood) the server might come with a Type Library (.tlb) that you could use to lookup interface functions.</p>
<p>I won’t go any deeper into type libraries or anything else here (the blog post is quite long already), but my general recommendation for someone in this situation would be: Take my sample RPC client and server code, compile it and start your reverse engineering journey with sample code you know. In that particular case, let me add another clue: My sample interface has an “Output” function defined in the IDL file, this “Output” function begins with the print statement <code class="language-plaintext highlighter-rouge">printf("[~] Client Message: %s\n", pszOutput);</code>, you could for example start by searching for the substring <code class="language-plaintext highlighter-rouge">[~] Client Message</code> to figure out where this particular interface function is.</p>
<h3 id="client-impersonation">Client Impersonation</h3>
<p>Client impersonation also provides interesting attack surface. I’ve already put some light on what Impersonation is and how it works in the last part of the series, if you missed that and need a fresh up on Impersonation you will find that bit explained in the <a href="https://csandker.io/2021/01/10/Offensive-Windows-IPC-1-NamedPipes.html#impersonation">Impersonation Section of my last post</a>.</p>
<p>The recipe for impersonating a client is as follows:</p>
<ul>
<li>You need a RPC client connecting to your server</li>
<li>The client must use an authenticated binding (otherwise there would be no security information you could impersonate)</li>
<li>The client must not set the Impersonation Level authenticated binding below <em>SecurityImpersonation</em></li>
<li>… that’s it</li>
</ul>
<p>The process of impersonation is as easy as:</p>
<ul>
<li>Calling <a href="https://docs.microsoft.com/en-us/windows/win32/api/rpcdce/nf-rpcdce-rpcimpersonateclient">RpcImpersonateClient</a> from within your server interface function<br />
<em>Note that this function takes the binding handle as input, therefore you need a Explicit binding server to use impersonation (which makes sense)</em></li>
<li>If that call succeeds, the server’s thread context is changed to the client’s security context and you can call <a href="https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getcurrentthread">GetCurrentThread</a> & <a href="https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-openthreadtoken">OpenThreadToken</a> to receive the client’s Impersonation token.<br />
<em>If you’re now like ‘WTF security context change?!’ you will find answers <a href="https://csandker.io/2021/01/10/Offensive-Windows-IPC-1-NamedPipes.html#impersonating-a-named-pipe-client">in the IPC Named Pipe post</a></em><br />
<em>if you’re more like ‘WTF Impersonation token?!’ you will find answers in my <a href="https://csandker.io/2018/06/14/AWindowsAuthorizationGuide.html#access-tokens">Windows Authorization Guide</a></em></li>
<li>Once you’ve called <a href="https://docs.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-duplicatetokenex">DuplicateTokenEx</a> to turn your Impersonation token into a primary token, you can happily return to your original server thread context by calling <a href="https://docs.microsoft.com/en-us/windows/win32/api/rpcdce/nf-rpcdce-rpcreverttoself">RpcRevertToSelfEx</a></li>
<li>And finally you can call <a href="https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createprocesswithtokenw">CreateProcessWithTokenW</a> to create a new process with the client’s token.</li>
</ul>
<p>Please note that this is only one way to create a process with the client’s token, but in my eyes it pictures the way of doing these things pretty well and therefore i use this approach here. A sample implementation of this code can be found <a href="https://github.com/csandker/InterProcessCommunication-Samples/blob/master/RPC/CPP-RPC-Client-Server/RPC-Util/Command.cpp#L255">here</a>.<br />
This is by the way the same procedure i used for impersonating Named Pipe clients in my last post.</p>
<p>As said in the recipe steps above, you just need a client that connects to your server and that client must use an authenticated binding.<br />
If the client does not authenticate its binding that the call to <a href="https://docs.microsoft.com/en-us/windows/win32/api/rpcdce/nf-rpcdce-rpcimpersonateclient">RpcImpersonateClient</a> will result in Error 1764 (RPC_S_BINDING_HAS_NO_AUTH).<br />
Finding a suitable client that you can make connect to your server comes down to finding a RPC client (see section <a href="#rpc-clients">Finding RPC Clients</a>) and finding one that you can make connect to your server. Well the later might be the tricky part in this exploit chain and I can’t give general recommendation here on how to find those connections. One reason for that is because it depends on the protocol sequence used by the client, where an unanswered TCP call might be best detectable when sniffing on the wire where an unanswered Named Pipe connection attempt could also be spotted on the client’s or server’s host system.<br /></p>
<p>In the 1st part of the series (which was about Named Pipes) I pulled a bigger spotlight on client impersonation, therefore i will safe myself a few words here. However, if you haven’t already done it I would recommend reading up on <a href="https://csandker.io/2021/01/10/Offensive-Windows-IPC-1-NamedPipes.html#instance-creation-race-condition">Instance Creation Race Conditions</a> and also <a href="https://csandker.io/2021/01/10/Offensive-Windows-IPC-1-NamedPipes.html#instance-creation-special-flavors">Instance Creation Special Flavors</a>. The same principals apply here.</p>
<p>The more interesting aspect is that I intentionally wrote above: “The client must not set the Impersonation Level authenticated binding below SecurityImpersonation* … which sounds kinda like an opt-out process and that’s exactly what it is.<br />
Remember that you can set the <a href="https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-security_quality_of_service">Quality of Service (QOS)</a> structure on the client side when creating an authenticated binding? As said back in section <a href="#authenticated-bindings">Authenticated Bindings</a> you can use that structure to determine the Impersonation Level when connecting to the server. <strong>Interestingly if you don’t set any QOS structure the default will be SecurityImpersonation</strong>, which allows any server to impersonate an RPC client as long as the client does not set the Impersonation Level explicitly below <em>SecurityImpersonation</em>.</p>
<p>The result of an impersonation could then look like this:</p>
<p><img src="/public/img/2021-02-21-Offensive-Windows-IPC-2-RPC/RPC_Impersonating_Client.png" alt="RPC Impersonation" /></p>
<h3 id="server-non-impersonation">Server Non-Impersonation</h3>
<p>There is another side of impersonation that is often missed, but is not less interesting from an attackers perspective.<br />
In <a href="https://csandker.io/2021/01/10/Offensive-Windows-IPC-1-NamedPipes.html#impersonating-a-named-pipe-client">part 1 of the series</a> i detailed the steps that are involved when impersonating a client, these equally apply also for RPC impersonations (and in all other similar technologies), where the following two steps are especially interesting:</p>
<blockquote>
<p>>> Step 8: The server’s thread context is then changed to the client’s security context.<br />
>> Step 9: Any action the server takes and any function the server calls while in the security context of the client are made with the identify of the client and thereby impersonating the client.<br />
Source: <a href="https://csandker.io/2021/01/10/Offensive-Windows-IPC-1-NamedPipes.html#impersonating-a-named-pipe-client">Offensive Windows IPC Internals 1: Named Pipes</a></p>
</blockquote>
<p>The server’s thread context is changed and all actions then made are made with the security context of the client. In the above section (and in my <a href="https://github.com/csandker/InterProcessCommunication-Samples/blob/master/RPC/CPP-RPC-Client-Server/RPC-Util/Command.cpp#L255">sample code</a>) I used that to grab the current thread token, which then is the client’s token and transform that into a primary token to launch a new process with that token. I could as well just called any action i want to do directly, because I’m all ready operating in the client’s security context. Based on the section title you might already guess now where this is heading… what if the impersonation fails and the server does not check for that?</p>
<p>The call to <a href="https://docs.microsoft.com/en-us/windows/win32/api/rpcdce/nf-rpcdce-rpcimpersonateclient">RpcImpersonateClient</a>, the API function that does all the impersonation magic for you, returns the status of the impersonation operation and it is crucial for the server to check that.<br />
If the impersonation is successful you’re inside the client’s security context afterwards, but if it fails you’re in the same old security context from where you called the <a href="https://docs.microsoft.com/en-us/windows/win32/api/rpcdce/nf-rpcdce-rpcimpersonateclient">RpcImpersonateClient</a>.<br />
Now a RPC server is likely to run as another user (often also in a higher security context) and in those cases it might try to impersonate its clients to run client operations in a lower, presumably safer client security context. As an attacker you could use those cases for privilege escalation attack vectors by forcing a failing impersonation attempt on the server side and therefore causing the server to execute client operating in the higher security context of the server.</p>
<p>The recipe for this attack scenario is simple:</p>
<ul>
<li>You need a server that impersonates its clients and does not carefully check the return status of <a href="https://docs.microsoft.com/en-us/windows/win32/api/rpcdce/nf-rpcdce-rpcimpersonateclient">RpcImpersonateClient</a> before executing further actions.</li>
<li>The action taken by the server after an impersonation attempt must be exploitable from your client’s perspective.</li>
<li>You need to force the impersonation attempt to fail.</li>
</ul>
<p>Finding a local server that tries to impersonate a client is a simple task if you read the previous sections and took note of how to use <a href="https://docs.microsoft.com/en-us/cpp/build/reference/dumpbin-reference?view=msvc-160">DumpBin</a>.<br /></p>
<p>Finding a server that runs actions in a ‘assumed impersonated’ context which can be used from an attackers perspective is pretty much a creative case-by-case analysis of what the server does. The best advise to analyze those cases is to think outside the box and potentially be prepared to chain multiple events and actions. A rather simple but powerful example could be a file operation conducted by the server; Maybe you could use junctions to create a file in a write protected system path or maybe you could cause the server to open a Named Pipe instead of a file and then use Named Pipe Impersonation to impersonate the server…</p>
<p>Last on the checklist is causing the server’s impersonation attempt to fail and that’s the easiest part of the job. There are two ways to achieve this:</p>
<ul>
<li>You could connect from an unauthenticated binding; Or</li>
<li>You could connect from an authenticated binding and set the Impersonation Level of the QOS structure to <em>SecurityAnonymous</em></li>
</ul>
<p>Either of this actions will safely cause a failed impersonation attempt.<br />
This technique is by the way not a new thing, it’s widely known… just forgotten sometimes. Maybe there also is a more fancy name out there for this technique that i haven’t come across yet. Microsoft even especially reminds you of this in the Remarks section (they even gave this a special ‘Securtiy Remarks’ heading) of the <a href="https://docs.microsoft.com/en-us/windows/win32/api/rpcdce/nf-rpcdce-rpcimpersonateclient">RpcImpersonateClient</a> function:</p>
<blockquote>
<p>If the call to RpcImpersonateClient fails for any reason, the client connection is not impersonated and the client request is made in the security context of the process. If the process is running as a highly privileged account, such as LocalSystem, or as a member of an administrative group, the user may be able to perform actions they would otherwise be disallowed. Therefore it is important to always check the return value of the call, and if it fails, raise an error; do not continue execution of the client request.<br />
Source: <a href="https://docs.microsoft.com/en-us/windows/win32/api/rpcdce/nf-rpcdce-rpcimpersonateclient#security-remarks">RpcImpersonateClient: Security Remarks</a></p>
</blockquote>
<h3 id="mitm-authenticated-ntlm-connections">MITM Authenticated NTLM Connections</h3>
<p>The last two sections cover the fact that RPC can be used as a remote networking communication technology and therefore also comes with an interesting attack surface on the network side.<br />
Side Note: I intentionally phrased it this way; Your initially though might have been “Dooough what else do you gonna use a technology called <u>Remote</u> Procedure Call for?!” … But in fact RPC is very well also intended to be used purely locally as a wrapper for ALPC (i get back to this in part 3 of the series once i figured out all the mysteries of ALPC).</p>
<p>Anyhow, if you’re using RPC over the wire and you want your binding to be authenticated you will need a network protocol that does the authentication for you. That’s why the second parameter (<em>AuthnSvc</em>) of the <a href="https://docs.microsoft.com/en-us/windows/win32/api/rpcdce/nf-rpcdce-rpcserverregisterauthinfo">RpcServerRegisterAuthInfo</a>, which is the API function you’d call on the server side to create an authenticated binding, let’s you define which authentication service you would like to use. You could for example specify Kerberos with the constant value of <em>RPC_C_AUTHN_GSS_KERBEROS</em>, or you could specify <em>RPC_C_AUTHN_DEFAULT</em> to use the default authentication service, which is - interestingly enough - NTLM (<em>RPC_C_AUTHN_WINNT</em>).<br />
Kerberos was set to be the default authentication scheme since Windows 2000, but RPC still defaults to NTLM.</p>
<p>So if you’re in suitable position on the network and see a NTLM connection coming by there are two interesting things you could do with that:</p>
<ul>
<li>You could grab the NTLM(v2) challenge response hash off the wire and offline brute-force the user’s password; And/Or</li>
<li>You could intercept and relay the NTLM connection to gain access to another system.</li>
</ul>
<p>I don’t want to deep dive into those two topics (if you made it until here you sure have read enough already), so I’ll add just two remarks here:</p>
<ul>
<li>NTLM(v2) challenge brute-forcing is very well known, so you should not have trouble finding how to do that. Check out hashcat mode 5600 on <a href="https://hashcat.net/wiki/doku.php?id=example_hashes">https://hashcat.net/wiki/doku.php?id=example_hashes</a> for an example.</li>
<li>NTLM Relay is very well described by the great <a href="https://twitter.com/HackAndDo">Pixis</a> at <a href="https://en.hackndo.com/ntlm-relay/">https://en.hackndo.com/ntlm-relay/</a>. There are a few things to be aware of depending on the protocol used so make sure you check out that post if you’re interested.</li>
</ul>
<h3 id="mitm-authenticated-gss_negotiate-connections">MITM Authenticated GSS_NEGOTIATE Connections</h3>
<p>Last but not least… you nearly made it through this post.<br />
Next to NTLM based network authentication schemes, which is what you get if you chose <em>RPC_C_AUTHN_WINNT</em> or <em>RPC_C_AUTHN_DEFAULT</em> as the authentication service in your <a href="https://docs.microsoft.com/en-us/windows/win32/api/rpcdce/nf-rpcdce-rpcserverregisterauthinfo">RpcServerRegisterAuthInfo</a> call, the very often used <em>RPC_C_AUTHN_GSS_NEGOTIATE</em> constant is also an interesting target.<br />
If <em>RPC_C_AUTHN_GSS_NEGOTIATE</em> is chosen <a href="https://docs.microsoft.com/en-us/windows/win32/secauthn/microsoft-negotiate">Microsoft’s Negotiate SSP</a> is used to instruct the client and server to negotiate on their own if NTLM or Kerberos should be used to authenticate users. By default this negotiation will always result in Kerberos if client and server support it.</p>
<p>This negotiation can be attacked from an intercepting network position to force the usage of NTLM over Kerberos, effectively downgrading the authentication scheme. The caveat is that this attack requires a suitable network position and missing signatures. I will not dive deeper into this at this point, mostly cause I’ve detailed the process and the attack in an older post here: <a href="https://csandker.io/2018/04/04/SPNEGODown.html">Downgrade SPNEGO Authentication</a>.</p>
<p>By the way the authentication service constants that mentioned here can be found here: <a href="https://docs.microsoft.com/en-us/windows/win32/rpc/authentication-service-constants">https://docs.microsoft.com/en-us/windows/win32/rpc/authentication-service-constants</a>.</p>
<p>That’s it.. you made it!</p>
<h2 id="references">References</h2>
<ul>
<li>Microsoft’s documentation of RPC: <a href="https://docs.microsoft.com/en-us/windows/win32/rpc/overviews">https://docs.microsoft.com/en-us/windows/win32/rpc/overviews</a></li>
<li><a href="https://twitter.com/jsecurity101">Jonathan Johnson’s</a> (more defensive focused) Review of RPC: <a href="https://ipc-research.readthedocs.io/en/latest/subpages/RPC.html">https://ipc-research.readthedocs.io/en/latest/subpages/RPC.html</a></li>
<li><a href="https://twitter.com/_xpn_">Adam Chester’s</a> Review of RPC: <a href="https://blog.xpnsec.com/analysing-rpc-with-ghidra-neo4j/">https://blog.xpnsec.com/analysing-rpc-with-ghidra-neo4j/</a></li>
<li>A Code Project on how to start programming with RPC: <a href="https://www.codeproject.com/Articles/4837/Introduction-to-RPC-Part-1#Implicitandexplicithandles17">https://www.codeproject.com/Articles/4837/Introduction-to-RPC-Part-1#Implicitandexplicithandles17</a></li>
</ul>Contents: