Skip to content

Instantly share code, notes, and snippets.

@Dump-GUY
Created November 20, 2023 17:06
Show Gist options
  • Save Dump-GUY/5e3f053a252307ba133a4e5b6cfeacb0 to your computer and use it in GitHub Desktop.
Save Dump-GUY/5e3f053a252307ba133a4e5b6cfeacb0 to your computer and use it in GitHub Desktop.
# Get IL code and pre-compiled native code disassembly of R2R Assembly methods
# Using AsmResolver + Iced + PowerShell
# More Info Here: https://docs.washi.dev/asmresolver/guides/peimage/ready-to-run.html
# Loading dependecies
[System.Reflection.Assembly]::LoadFrom([System.IO.Path]::GetFullPath(".\libs\AsmResolver\net6.0\AsmResolver.PE.dll")) | Out-Null
[System.Reflection.Assembly]::LoadFrom([System.IO.Path]::GetFullPath(".\libs\AsmResolver\net6.0\AsmResolver.DotNet.dll")) | Out-Null
[System.Reflection.Assembly]::LoadFrom([System.IO.Path]::GetFullPath(".\libs\Iced\netstandard2.1\Iced.dll")) | Out-Null
$filePath = [System.IO.Path]::GetFullPath(".\test_files\CompileDecoy_ReplaceReal_SC_Original.dll") # R2R Assembly Sample
$moduleDef = [AsmResolver.DotNet.ModuleDefinition]::FromFile($filePath)
$peImage = [AsmResolver.PE.PEImage]::FromFile($filePath)
# Check if it is a R2R Assembly
if ($peImage.DotNetDirectory.ManagedNativeHeader -isnot [AsmResolver.PE.DotNet.ReadyToRun.ReadyToRunDirectory])
{
Write-Host "Not R2R Assembly!!!" -ForegroundColor Red
return
}
# Getting RuntimeFunctions section - contains info about pre-compiled functions
$R2RDirectory = $peImage.DotNetDirectory.ManagedNativeHeader
$RFSection = $null
if (!$R2RDirectory.TryGetSection([AsmResolver.PE.DotNet.ReadyToRun.ReadyToRunSectionType]::RuntimeFunctions, [ref] $RFSection))
{
Write-Host "R2R Assembly does not have a RuntimeFunctions section!!!" -ForegroundColor Red
return
}
# Getting MethodDefEntryPoints section - to later find out what pre-compiled code corresponds to what method
$MEPSection = $null
if (!$R2RDirectory.TryGetSection([AsmResolver.PE.DotNet.ReadyToRun.ReadyToRunSectionType]::MethodDefEntryPoints, [ref] $MEPSection))
{
Write-Host "R2R Assembly does not have a MethodDefEntryPoints section!!!" -ForegroundColor Red
return
}
# Loop all EntryPoints in MethodDefEntryPoints section - in order
for ($i = 0; $i -lt $MEPSection.EntryPoints.Count; $i++)
{
# Create Token that should correspond to EntryPoint function - because of ordering
$token = [AsmResolver.PE.DotNet.Metadata.Tables.MetadataToken]::new([AsmResolver.PE.DotNet.Metadata.Tables.TableIndex]::Method, ($i + 1))
$entrypoint = $MEPSection.EntryPoints[$i]
if ($null -eq $entrypoint)
{
Write-Host "Method Token:$token is not mapped to a runtime function!!!" -ForegroundColor Red
continue
}
# Find the method in module definition using the calculated Token
$method = $null
if (!$moduleDef.TryLookupMember($token, [ref] $method))
{
Write-Host "Method with Token:$token is not found in Module!!!" -ForegroundColor Red
continue
}
# Check if CIL Body of the method not empty
if ($null -eq $method.CilMethodBody)
{
Write-Host "Method Name:'$($method.Name.ToString())' with Token:$token has no IL body!!!" -ForegroundColor Red
continue
}
# Print Method Name, Token, IL code - Instructions
Write-Host "`nMethod Name:'$($method.Name.ToString())' Token:$token" -ForegroundColor Green
Write-Host "IL Body:" -ForegroundColor Green
$formatter = [AsmResolver.PE.DotNet.Cil.CilInstructionFormatter]::new()
foreach ($inst in $method.CilMethodBody.Instructions)
{
Write-Host ($formatter.FormatInstruction($inst)) -ForegroundColor Yellow
}
# Get the opcode bytes of the pre-compiled native code of the method
Write-Host "Native Pre-Compiled Body - Disassembly:" -ForegroundColor Green
$runtimeFunction = $RFSection.Functions[$entrypoint.RuntimeFunctionIndex]
if (!$runtimeFunction.Begin.CanRead)
{
Write-Host "Can NOT Read Native Opcode bytes of Method Name:'$($method.Name.ToString())' with Token:$token!!!" -ForegroundColor Red
continue
}
$length = $runtimeFunction.End.Rva - $runtimeFunction.Begin.Rva
[byte[]]$buffer = [byte[]]::new($length)
$runtimeFunction.Begin.CreateReader().ReadBytes($buffer, 0, $length) | Out-Null
# Setup for the disassembler and disassembly formatter - using Iced for opcode bytes disassembling
if ($peImage.MachineType -eq [AsmResolver.PE.File.Headers.MachineType]::Amd64)
{
$codeBitness = 64
}
else {$codeBitness = 32}
$HEXBYTES_COLUMN_BYTE_LENGTH = 10
$codeRIP = $runtimeFunction.Begin.Rva
$codeBytes = $buffer
$codeReader = [Iced.Intel.ByteArrayCodeReader]::new($codeBytes)
$decoder = [Iced.Intel.Decoder]::Create($codeBitness, $codeReader)
$decoder.IP = $codeRIP
$endRip = $decoder.IP + $codeBytes.Length
# Disassembly instructions
$instructions = New-Object Collections.Generic.List[Iced.Intel.Instruction]
while ($decoder.IP -lt $endRip)
{
$instructions.Add($decoder.Decode())
}
# Format and print the disassembly of the pre-compiled native code of the method
$formatter = [Iced.Intel.NasmFormatter]::new()
$formatter.Options.DigitSeparator = '`'
$formatter.Options.FirstOperandCharIndex = 10
$output = [Iced.Intel.StringOutput]::new()
foreach ($instr in $instructions)
{
$formatter.Format([ref] $instr, $output)
Write-Host ($instr.IP.ToString("X16")) -NoNewline -ForegroundColor Blue
Write-Host " " -NoNewline -ForegroundColor Blue
$instrLen = $instr.Length
$byteBaseIndex = [int]($instr.IP - $codeRIP)
for ($j = 0; $j -lt $instrLen; $j++)
{
Write-Host ($codeBytes[$byteBaseIndex + $j].ToString("X2")) -NoNewline -ForegroundColor Blue
}
$missingBytes = $HEXBYTES_COLUMN_BYTE_LENGTH - $instrLen
for ($k = 0; $k -lt $missingBytes; $k++)
{
Write-Host " " -NoNewline -ForegroundColor Blue
}
Write-Host " " -NoNewline -ForegroundColor Blue
Write-Host ($output.ToStringAndReset()) -ForegroundColor Blue
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment