Hijacking PowerShell commands: Masquerading persistence on the system
¡Hola lectores!
En el artículo de hoy hablaremos de cómo poder realizarhijackingde comandos de PowerShell orientado a hacerbypassde los AV/EDR en ejercicios de Red y Purple Team. Aunque en la siguiente entrada se ha usado esta técnica con el fin de enmascarar la persistencia en el sistema, este método puede ser utilizado para secuestrar cualquier comando de PowerShell (cmdlet) con el fin de ejecutar cualquier otro tipo de código que se desee. El flujo de ejecución que se va a emular es el siguiente:
Inicialmente se va a empezar hablando de tres conceptos necesarios para llevar a cabo el hijacking o secuestro.
El primero es el concepto de cmdlet. Los cmdlets son los comandos de PowerShell nativos. PowerShell usa el formato verbo-nombre para asignar nombres a los cmdlets. Por ejemplo, el cmdlet Start-Process incluido en PowerShell se usa para arrancar un proceso. El verbo identifica la acción que realiza el cmdlet y el nombre identifica el recurso en el que el cmdlet realiza la acción.
El segundo es el de los perfiles de PowerShell. Según la propia documentación de Microsoft, un perfil de PowerShell es un script que se ejecuta cada vez que se arranca un proceso de PowerShell y es empleado con el fin de añadir elementos (comandos, funciones, alias, variables, etcétera) que se encontrarán disponibles en todas las sesiones sin necesidad de volver a crearlos o importarlos. Por ejemplo, si en un perfil de PowerShell se incluye la declaración de la variable $myVar = ‘Esto es una prueba’, una vez se arranque PowerShell, se habrá creado automáticamente la variable $myVar con el contenido ‘Esto es una prueba’ sin necesidad de volver a declarar ésta en cada sesión.
El tercero es el concepto de las funciones proxy, las cuales son “contenedores” de una función, que realmente actúan como un launcher de la misma a efectos prácticos. Cuando se genera una función proxy, se tiene acceso tanto a la Steppable Pipeline (la cual es empleada para controlar la ejecución) como a los parámetros pasados a ésta, por lo se puede controlar la llamada en su totalidad.
Una vez se han expuesto estos conceptos, se procederá con la prueba de concepto.
-
Lo primero que se tiene que hacer es generar la función proxy del comando al que se quiere hacer hijacking.
$Function1 = New-Object System.Management.Automation.CommandMetaData (Get-Command Write-Output)
[System.Management.Automation.ProxyCommand]::Create($Function1) | Out-File -FilePath "C:\Users\Public\Original_write_output.ps1Obteniendo el siguiente script:
-
Posteriormente se genera la función proxy del comando Start-Process (o de la función por la cual se quiera reemplazar).
$Function2 = New-Object System.Management.Automation.CommandMetaData (Get-Command Start-Process)
[System.Management.Automation.ProxyCommand]::Create($Function) | Out-File -FilePath "C:\Users\Public\Original_Start_process.ps1En este caso se obtiene una función con la misma estructura, aunque se pueden apreciar dos diferencias. La primera es que en este caso los parámetros de entrada de la función son los de Start-Process.
La segunda es que la línea “$wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Microsoft.PowerShell.Management\Start-Process', [System.Management.Automation.CommandTypes]::Cmdlet)” es diferente.
-
Si se reemplaza esa línea (la que contiene la variable $wrappedCmd) en la función proxy de Write-Output, se ejecutará un Start-Process en vez de el Write-Output original. Sin embargo, ocurren dos problemas en este caso.
- El primero es que los parámetros pasados a la función Write-Output no son iguales que los de la función Start-Process. Para arreglar esto, hay que reemplazar la variable $PSBoundParameters (que es una hashtable) por la deseada:
$PSBoundParameters.Remove('InputObject');
$PSBoundParameters.Remove('NoEnumerate');
$PSBoundParameters.Add('PSPath', 'C:\Users\Public\payload.exe'); - El segundo es que ahora se ha reemplazado por completo la función Write-Output, dejándola inservible (siempre se ejecutará un Start-Process). Para arreglar este problema, se hará que se ejecute nuestro hijacking únicamente cuando se llame a Write-Output con una cadena concreta (en este caso, la palabra “hijack”. Para ello, se ha agregado la siguiente sentencia if:
if ($PSBoundParameters.InputObject[0] -clike '*hijack*'){
Execute Hijacking
}else{
Execute original Write-Output
}
- El primero es que los parámetros pasados a la función Write-Output no son iguales que los de la función Start-Process. Para arreglar esto, hay que reemplazar la variable $PSBoundParameters (que es una hashtable) por la deseada:
-
El código final de la función proxy modificada queda de la siguiente manera (hay que tener en cuenta que hay que añadir el código en una función con el nombre Write-Output, ya que por defecto cuando se genera la función proxy no se implementa esta declaración).
function Write-Output{
Our modified code goes here
} -
Una vez se tiene la función proxy Write-Output modificada, se debe reemplazar la función original (cmdlet Write-Output) por la nuestra. En PowerShell, cuando se importa una función con el mismo nombre que cualquier cmdlet, ésta reemplaza a la original. Sin embargo, se necesita que esta función se cargue siempre que se inicie PowerShell y para ello se van a emplear los perfiles de PowerShell. Inicialmente, se comprueba si existe algún perfil activo en el sistema para modificarlo. En caso contrario, se crea uno nuevo y se incluye el script generado anteriormente (función hijackeada) codificada en base 64.
Comentar que incluir una cadena en base 64 en un script de PowerShell no es óptimo a la hora de hacer bypass del EDR aunque en este caso, se ha decidido hacer así por simplicidad y limpieza de código.
-
Una vez se ha creado el perfil con nuestro código, cada vez que se inicie una instancia de PowerShell, se lanzará nuestro Write-Output modificado en vez del original, y cuando se invoque cualquier sentencia que contenga la palabra “hijack”, se ejecutará el payload.
-
Por tanto, ahora solo bastaría con añadir persistencia en el sistema a través del método que se desee de manera que se ejecute el siguiente comando:
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -command "Write-Output 'hijack prueba'"
Referencias:
- https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_profiles?view=powershell-7.3
- https://learn.microsoft.com/es-es/powershell/scripting/powershell-ommands?view=powershell-7.3
- https://devblogs.microsoft.com/scripting/proxy-functions-spice-up-your-powershell-core-cmdlets/
- https://learn.microsoft.com/en-us/dotnet/api/system.management.automation.steppablepipeline?view=powershellsdk-7.3.0