OSEP, Research

How to run notepad.exe with Powershell

L’articolo che segue non ha la presunzione di essere una guida o un tutorial, è stato scritto con il solo scopo di annotare le nozioni apprese.

Glossario

Assembly: un assembly in C# è una raccolta di tipi e risorse creati per collaborare e formare un’unità logica di funzionalità. In parole povere, in C#, un assembly è un file .exe o .dll.

Reflection: è l’abilità di un programma di aggiornare programmaticamente il codice del programma stesso, modificarne la struttura, creare degli assembly.

Function prototype: in programmazione è la dichiarazione della funzione stessa, con i parametri previsti e il valore di ritorno, escluso il corpo della funzione. Fornisce al compilatore le info necessarie per gestirla.

Delegate type: è la function prototype di C#.

Managed code: è il codice creato dai compilatori VB .NET e C#. Si esegue nel CLR (Common Language Runtime). VB e C# producono solo managed code, quindi CLR è sempre necessario. Visual C++ può produrre managed code ma è opzionale.

CLR: Common Language Runtime. CLR prende il managed code, lo compila in machine code e lo esegue.

Unmanaged code: è il codice compilato direttamente in codice macchina. Tutto il codice tradizionale compilato C/C++ è unmanaged code. VB e C# non posson creare unmanaged code.

Obiettivo

Il titolo del post è goliardico, l’obiettivo reale è utilizzare powershell per interagire con le API di Windows (Win32Api) per utilizzare le varie funzionalità messe a disposizione dalle dll.

In questo caso andrò ad utilizzare la funzione WinExec situata all’interno di kernel32.dll per aprire il Notepad di windows.

Dynamic Lookup

La prima cosa da fare è localizzare l’indirizzo di memoria in cui risiede la funzione WinExec:

function LookupFunc {
    Param ($moduleName, $functionName)
    $assem = ([AppDomain]::CurrentDomain.GetAssemblies() |Where-Object { 
        $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals('System.dll') 
    }).GetType('Microsoft.Win32.UnsafeNativeMethods')
    $tmp=@()
    $assem.GetMethods() | ForEach-Object {
        If($_.Name -eq "GetProcAddress") {
            $tmp+=$_
        }
    } 
    return $tmp[0].Invoke($null, @(($assem.GetMethod('GetModuleHandle')).Invoke($null,@($moduleName)), $functionName)) 
}

Non mi dilungherò troppo su questa funzione perché resta invariata, il suo scopo è e sarà sempre quello di ottenere l’indirizzo in memoria di una determinata funzione di una dll.

Questo avviene attraverso le Win32Api GetModuleHandle e GetProcAddress che restituiscono rispettivamente l’indirizzo di memoria della dll e l’indirizzo in memoria della funzione ricercata.

Il tutto avviene ricercando le due funzionalità in una lista di assembly caricati nel processo di Powershell (GetAssemblies).

La funzione LookupFunc può essere usata come segue:

Funzione LookupFunc

Che restituisce il valore decimale dell’indirizzo in memoria di WinExec.

Nel momento in cui abbiamo risolto l’indirizzo della funzione che ci interessa bisogna definire tutti gli argomenti previsti dal function prototype per poterla richiamare correttamente. Questo in C# si fa con GetDelegateForFunctionPointer. Questo metodo accetta due argomenti, il primo è l’indirizzo di memoria della funzione che ci interessa, il secondo è il function prototype della funzione oggetto di interesse rappresentato come un tipo.

Per passare questo secondo argomento occorre creare un tipo custom. Per poterlo fare bisogna definire un delegate type, un oggetto custom, in modo da far combaciare il numero di argomenti e il tipo di dati degli stessi con quelli previsti dalla funzione rilevata in memoria.

Per capire che tipo di argomenti si aspetta la funzione che ci interessa si può utilizzare Pinvoke.net:

https://www.pinvoke.net/default.aspx/kernel32/WinExec.html

PInvoke.net è un sito che consente agli sviluppatori di trovare, modificare e aggiungere firme PInvoke (native method signatures) o tipi definiti dall’utente e qualsiasi altra informazione correlata alla chiamata di Win32 e di altre unmanaged API dal managed code, quindi da C# o VB. In sostanza fornisce un’interfaccia tra le dll sviluppate in C/C++ e il nostro codice C#, per esempio.

In C# un delegate type può essere dichiarato come segue:

int delegate MessageBoxSig(IntPtr hWnd, String text, String caption, int options);

Purtroppo la keyword delegate in Powershell non esiste, quindi per creare un delegate type bisogna utilizzare la Reflection.

Con l’utilizzo della reflection si creerà a runtime, programmaticamente, totalmente compilata in memoria, una dll. La dll conterrà una funzione che potrà essere richiamata a runtime.

function CustomDelegateType {
    Param ([Type[]] $params, [Type] $retval = [Void])
    
    $MyAssembly = New-Object System.Reflection.AssemblyName('ReflectedDelegate') 
    # Creo un assembly a runtime. ReflectDelegate è il nome dell'assembly, può essere qualsiasi stringa.


    $Domain = [AppDomain]::CurrentDomain 
    $MyAssemblyBuilder = $Domain.DefineDynamicAssembly($MyAssembly,[System.Reflection.Emit.AssemblyBuilderAccess]::Run)
    # Per non salvare nulla su disco si assegna all'assembly solo i permessi di esecuzione (::Run) così da non scrivere nulla su disco


    $MyModuleBuilder = $MyAssemblyBuilder.DefineDynamicModule('InMemoryModule', $false)
    # Qui avviene la creazione del contenuto dell'assembly. Il blocco di costruzione di un assembly è chiamato Module
    # Il Module si crea con DefineDynamicModule e sarebbe il codice che compone un assembly
    # InMemoryModule è un nome qualsiasi, $false serve per non includere simboli nell'assembly
    

    $MyTypeBuilder = $MyModuleBuilder.DefineType('MyDelegateType','Class, Public, Sealed, AnsiClass, AutoClass', [System.MulticastDelegate]) 
    # A questo punto si crea il tipo custom (delegate type) con DefineType 
    # Bisogna passare 3 argomenti: Nome tipo custom, lista di attributi del tipo custom e il tipo su cui si basa il tipo custom creato
    # Parametro 2 esploso:
    # Class: Sarà una classe cosìcché possa essere istanziata
    # Public: Sarà pubblica
    # Sealed: non estendibile, quindi altre classi non possono ereditare da questa classe
    # AnsiClass: Userà ASCII
    # AtoClass: LPTSTR sarà interpretato automaticamente. (Doc MS non chiara) => LPTSTR is a pointer to a (non-const) TCHAR string.
    # MulticastDelegate è un tipo che può avere più di un argomento quando viene invocato



    $MyConstructorBuilder = $MyTypeBuilder.DefineConstructor('RTSpecialName, HideBySig, Public',[System.Reflection.CallingConventions]::Standard, $params)
    # Creando il costruttore si definisce ufficialmente il custom delegate type
    # Per definire un costruttore servono 3 argomenti. Il primo agomento contiene le caratteristiche del costruttore stesso.
    # Il secondo argomento è la calling convention che .NET deve usare
    # Il terzo argomento sono il tipo dei parametri accettati costruttore, questo diventa ufficialmente la function prototype. 
    # In questo caso saranno gli argomenti di WinExec.
    # Parametro 1 esploso:
    # RTSpecialName: specifica che Common Language Runtime (API interne dei metadati) deve controllare la codifica dei nomi.
    # HideBySig: è un'istruzione che garantisce che nessun metodo in una classe derivata abbia la stessa firma quando viene interpretata
    # Public è un metodo pubblico



    $MyConstructorBuilder.SetImplementationFlags('Runtime, Managed')
    # Prima di richiamare il costruttore occorre impostare un paio di flag:
    # Runtime: Specifica che l'implementazione del metodo è fornita dal runtime. .NET runtime è CLR (Common Language Runtime)
    # Managed: Specifica che il metodo è implementato in managed code. 

 
    $MyMethodBuilder = $MyTypeBuilder.DefineMethod('Invoke','Public, HideBySig, NewSlot, Virtual', $retval, $params)
    # DefineMethod è usato per creare un metodo all'interno della classe
    # Accetta quattro parametri: Il primo è il nome custom del metodo con cui può essere invocato
    # Il secondo sono le caratterisiche del metodo
    # Il terzo è il return type
    # Il quarto è un array con il tipo di argomenti accettati dal metodo
    # In questo caso gli argomenti saranno gli stessi del costruttore
    # Parametro 2 esploso:
    # Public: metodo pubblico
    # HideBySig è un'istruzione che garantisce che nessun metodo in una classe derivata abbia la stessa firma quando viene interpretata
    # NewSlot indica che il metodo ottiene sempre un nuovo slot nella vtable.
    # vtable è la tabella dei metodi virtuali. Una collezione di puntatori a metodi virtuali che ogni classe ha
    # Virtual viene utilizzato per modificare un metodo e consentirne l'override in una classe che lo eredita



    $MyMethodBuilder.SetImplementationFlags('Runtime, Managed') 
    # Stessa solfa di prima


    $MyDelegateType = $MyTypeBuilder.CreateType()
    # Per istanziare l'oggetto custom si usa CreateType che richiama il costruttore custom

    return $MyDelegateType
    # Infine la funzione ritorna il tipo custom
}

Il tipo custom può essere finalmente utilizzato con GetDelegateForFunctionPointer che restituirà l’assembly creato a runtime e ci permetterà di richiamare il metodo Invoke creato nel codice soprastante.

$WinExec = LookupFunc kernel32.dll WinExec 
$MyDelegateType = CustomDelegateType @([String], [int]) int
# Per creare il delegate type custom si passano un array con il tipo degli argomenti che la funzione WinExec si aspetta
# E come secondo argomento il tipo di ritorno 

$MyFunction = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($WinExec, $MyDelegateType)
# Converte il puntatore a una funzione dll in un delegate type. In questo caso converte WinExec in un delegate type

$MyFunction.Invoke("notepad.exe",5)
# A questo punto si puà richiamare il metodo custom Invoke che permetterà di aprire il notepad

Notepad.exe

Di seguito il codice completo, ripulito dai commenti:

function LookupFunc {
    Param ($moduleName, $functionName)
    $assem = ([AppDomain]::CurrentDomain.GetAssemblies() |Where-Object { 
        $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals('System.dll') 
    }).GetType('Microsoft.Win32.UnsafeNativeMethods')
    $tmp=@()
    $assem.GetMethods() | ForEach-Object {
        If($_.Name -eq "GetProcAddress") {
            $tmp+=$_
        }
    } 
    return $tmp[0].Invoke($null, @(($assem.GetMethod('GetModuleHandle')).Invoke($null,@($moduleName)), $functionName)) 
}

function CustomDelegateType {
    Param ([Type[]] $params, [Type] $retval = [Void])
    
    $MyAssembly = New-Object System.Reflection.AssemblyName('ReflectedDelegate') 
    $Domain = [AppDomain]::CurrentDomain 
    $MyAssemblyBuilder = $Domain.DefineDynamicAssembly($MyAssembly,[System.Reflection.Emit.AssemblyBuilderAccess]::Run)
    $MyModuleBuilder = $MyAssemblyBuilder.DefineDynamicModule('InMemoryModule', $false)
    $MyTypeBuilder = $MyModuleBuilder.DefineType('MyDelegateType','Class, Public, Sealed, AnsiClass, AutoClass', [System.MulticastDelegate]) 
    $MyConstructorBuilder = $MyTypeBuilder.DefineConstructor('RTSpecialName, HideBySig, Public',[System.Reflection.CallingConventions]::Standard, $params)
    $MyConstructorBuilder.SetImplementationFlags('Runtime, Managed') 
    $MyMethodBuilder = $MyTypeBuilder.DefineMethod('Invoke','Public, HideBySig, NewSlot, Virtual', $retval, $params) 
    $MyMethodBuilder.SetImplementationFlags('Runtime, Managed') 
    $MyDelegateType = $MyTypeBuilder.CreateType()

    return $MyDelegateType
}

$WinExec = LookupFunc kernel32.dll WinExec 
$MyDelegateType = CustomDelegateType @([String], [int]) [int]
$MyFunction = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($WinExec, $MyDelegateType)
$MyFunction.Invoke("notepad.exe",5)