One of the reasons PowerShell is so powerful is in its ability to access to .NET framework. In theory, this should make the transition for C# developers seamless. Unfortunately, PowerShell is not quite a one-to-one translation of C#. I demonstrated where this is not the case in a previous post by illustrating the hoops you need to jump through to declare a delegate type in PowerShell using reflection. Declaring an enum or struct is no different.
Before I jump into the technical details, it's worth addressing the question you may have already formed. Why don't you just compile C# code using the Add-Type cmdlet? Well, that is a perfectly valid method of declaring a struct, enum, delegate, etc. However, compiling C# code leaves artifacts on the disk. To prove my point, just run Procmon while compiling C# in PowerShell. As someone with an attacker's mindset, I prefer that all operations take place in memory unless absolutely necessary. Fortunately, true memory residence can be achieved using reflection.
As an example, here is a portion of the code from my last PowerSploit release - Get-PEHeader:
$code = @"
using System;
using System.Runtime.InteropServices;
public class PE
{
public enum IMAGE_DOS_SIGNATURE : ushort
{
DOS_SIGNATURE = 0x5A4D, // MZ
OS2_SIGNATURE = 0x454E, // NE
OS2_SIGNATURE_LE = 0x454C, // LE
}
[StructLayout(LayoutKind.Sequential, Pack=1)]
public struct _IMAGE_DOS_HEADER
{
public IMAGE_DOS_SIGNATURE e_magic; // Magic number
public ushort e_cblp; // public bytes on last page of file
public ushort e_cp; // Pages in file
public ushort e_crlc; // Relocations
public ushort e_cparhdr; // Size of header in paragraphs
public ushort e_minalloc; // Minimum extra paragraphs needed
public ushort e_maxalloc; // Maximum extra paragraphs needed
public ushort e_ss; // Initial (relative) SS value
public ushort e_sp; // Initial SP value
public ushort e_csum; // Checksum
public ushort e_ip; // Initial IP value
public ushort e_cs; // Initial (relative) CS value
public ushort e_lfarlc; // File address of relocation table
public ushort e_ovno; // Overlay number
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 8)]
public string e_res; // May contain 'Detours!'
public ushort e_oemid; // OEM identifier (for e_oeminfo)
public ushort e_oeminfo; // OEM information; e_oemid specific
[MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst=10)]
public ushort[] e_res2; // Reserved public ushorts
public int e_lfanew; // File address of new exe header
}
}
"@
Add-Type -TypeDefinition $code -WarningAction SilentlyContinue | Out-Null
As you can see, the C# 'code' above defines an enum and a struct that are compiled when Add-Type is called. The need to compile the code above can be obviated with reflection. This however requires some understanding of the attributes that define the fields in the enum and struct above. Interrogating these attributes can be accomplished with ildasm or PowerShell. I'll show you how to do this in PowerShell.
First, let's view the attributes of the IMAGE_DOS_SIGNATURE enum:
PS > [PE+IMAGE_DOS_SIGNATURE] | Format-List BaseType, Attributes
BaseType : System.Enum
Attributes : AutoLayout, AnsiClass, Class, NestedPublic, Sealed
Now, let's view all the relevant individual attributes of the _IMAGE_DOS_HEADER struct:
PS > [PE+_IMAGE_DOS_HEADER] | Format-List BaseType, Attributes
BaseType : System.ValueType
Attributes : AutoLayout, AnsiClass, Class, NestedPublic, SequentialLayout, Sealed, BeforeFieldInit
PS > [PE+_IMAGE_DOS_HEADER].GetField('e_res').GetCustomAttributes($True) | Format-List Value, TypeId, SizeConst
Value : ByValTStr
TypeId : System.Runtime.InteropServices.MarshalAsAttribute
SizeConst : 8
PS > [PE+_IMAGE_DOS_HEADER].GetField('e_res2').GetCustomAttributes($True) | Format-List Value, TypeId, SizeConst
Value : ByValArray
TypeId : System.Runtime.InteropServices.MarshalAsAttribute
SizeConst : 10
Now we have everything we need to dynamically generate the enum and struct. The following code demonstrates how to accomplish this and returns the DOS header for calc.exe:
By now, you can see the additional overhead required to dynamically generate code. However, if your goal is to avoid compiling code and remain truly memory resident, this is the way to go. Expect to see an update to Get-PEHeader that will implement these changes in the near future.