Invoke-PowerTrashUnpacker — Автоматическая распаковка загрузчиков Powertrash

https://gist.github.com/antonioCoco/0ce9d3e1367506a05645d65318cc92ae

function Invoke-PowerTrashUnpacker{
    <#
        .SYNOPSIS
            Invoke-PowerTrashUnpacker - Automatically unpack Powertrash loaders
            Author: Antonio Cocomazzi @ SentinelOne
            License: MIT
        
        .DESCRIPTION
            Invoke-PowerTrashUnpacker allows you to automatically unpack Powertrash loaders.
            NOTE: Powershell AST and partial runtime evaluation are used for the unpacking.
                    Run this unpacker only on a testing virtual machine.
            
        .PARAMETER InputPath
            The path of the Powertrash loader or a directory containing Powertrash loaders
            
        .EXAMPLE  
            PS>Invoke-PowerTrashUnpacker 86533fff7813bc140c89bd2ed09b8484afe7e4ac.ps1
            
            Description
            -----------
            Unpack a ps1 powertrash sample
    #>
    Param
    (
        [Parameter(Position = 0, Mandatory = $True)]
        [String]
        $InputPath
    )
    # Manage both cases if InputPath is a folder or a file
    $files_to_unpack = @()
    if(Test-Path -Path $InputPath -PathType Container){
        Get-ChildItem -Path $InputPath -File -ErrorAction SilentlyContinue | ForEach-Object { $files_to_unpack += $_.FullName}
    }
    else{
        $files_to_unpack += (Resolve-Path $InputPath).ToString()
    }
    foreach ($file_to_unpack in $files_to_unpack){
        $script_code = Get-Content -Path $file_to_unpack -Raw
        # Checking the PowerTrash signature. We handle also the cases when the script is UTF16-LE encoded
        if($script_code -match 'function\s[0-9a-zA-Z]{3,7}\r?\n\{\r?\n(\$[0-9a-zA-Z]{3,7}=.*\r?\n){10}'){
            Write-Output "[*] The file '$file_to_unpack' matched the PowerTrash signature. Trying to unpack it."
        }
        else{
            # Trying to read the file as UTF16-LE
            $script_code = [System.IO.File]::ReadAllText($file_to_unpack, [System.Text.Encoding]::Unicode)
            if($script_code -match 'function\s[0-9a-zA-Z]{3,7}\r?\n\{\r?\n(\$[0-9a-zA-Z]{3,7}=.*\r?\n){10}'){
                Write-Output "[*] The file '$file_to_unpack' matched the PowerTrash signature. Trying to unpack it."
            }
            else{
                Write-Output "[-] The file '$file_to_unpack' is not PowerTrash. Skipping..."
                Continue
            }
        }
        # Using Powershell Abstract Syntax Tree (AST) to parse the code
        $AST_powertrash = [System.Management.Automation.Language.Parser]::ParseInput($script_code, [ref]$null, [ref]$null)
        # Search for all functions in the powershell script
        $functions_all = $AST_powertrash.FindAll({$args[0].GetType().Name -like "*FunctionDefinitionAst"}, $true)
        # Search for all commands in the powershell script
        $commands_all = $AST_powertrash.FindAll({$args[0].GetType().Name -like "*CommandAst"}, $true)
        # Last function invocation is the main Powertrash function
        $main_func = $commands_all[-1].Extent.Text 
        # Dinamically import all of the function definitions in our current process
        $functions_all | ForEach-Object { Invoke-Expression $_.Extent.Text}
        # We start to do our magic powershell unpacking from here, using AST to locate the base64 payload starting from the main
        $base64_packed_payload = ""
        $offset_start = 0x0
        $unpacked_payload_size = 0x0
        foreach ($function in $functions_all){
            # if we locate the main function name
            if ($function.Name -eq $main_func){
                # We use AST a second time only on the main function
                $AST_main = [System.Management.Automation.Language.Parser]::ParseInput($function.Extent.Text, [ref]$null, [ref]$null)
                # Getting all assignments from the main function
                $assignments = $AST_main.FindAll({$args[0].GetType().Name -like "*AssignmentStatementAst"}, $true)
                # The base64 payload is dinamically retrieved by invoking the function (right operand) of the first assignment in the main
                $base64_packed_payload = Invoke-Expression $assignments[0].Right.Extent.Text
                # Second assignment right operator contains the offset of the start address for the unpacked code
                $offset_start = $assignments[1].Right.Extent.Text
                # Third assignment right operator contains the size of the unpacked payload
                $unpacked_payload_size = $assignments[2].Right.Extent.Text
            }
        }
        # Base64 payload retrieved, now extracting the binary payload, just replicating what PowerTrash does
        $decoded_payload = [System.Convert]::FromBase64String($base64_packed_payload)
        $bytearray = [IO.MemoryStream][Byte[]]$decoded_payload
        $deflate_stream = New-Object IO.Compression.DeflateStream($bytearray, [IO.Compression.CompressionMode]::Decompress)
        $unpacked_payload = New-Object Byte[]($unpacked_payload_size)
        $bytes_read=$deflate_stream.Read($unpacked_payload, 0, $unpacked_payload_size)
        if($bytes_read -gt 0 -and $bytes_read -eq ([int]$unpacked_payload_size)){
            Write-Output "[+] Powershell code unpacked!"
        }
        else{
            Write-Output "[-] Powershell code couldn't be unpacked. Skipping..."
            continue
        }
        # Powershell code unpacked. Now we check if the unpacked payload is a pure PE
        $unpack_success = $false
        $pe_header_offset = [BitConverter]::ToInt32($unpacked_payload[60..63],0)
        if($pe_header_offset -lt $unpacked_payload.Length -and [char]$unpacked_payload[0] -eq 'M' -and [char]$unpacked_payload[1] -eq 'Z'){
            if(([char]$unpacked_payload[$pe_header_offset] -eq 'P') -and ([char]$unpacked_payload[$pe_header_offset+1] -eq 'E')){
                $output_path = $file_to_unpack + ".dll"
                Write-Output "[*] Unpacked content is a PE"
                Set-Content $output_path -Value $unpacked_payload -Encoding Byte
                Write-Output "[+] PE dumped to file $output_path"
                $unpack_success = $true
            }
        }
        # If not a pure PE, second we check if the unpacked payload is a PE with some prepended junk data
        if(-not $unpack_success){
            Write-Output "[!] Unpacked content is not a PE file, trying to walk the content for searching PE signatures..."
            for ($i=0; $i -lt $unpacked_payload.Length; $i++)
            {
                if([char]$unpacked_payload[$i] -eq 'M' -and [char]$unpacked_payload[$i+1] -eq 'Z'){
                    $mz_offset_candidate = $i
                    $pe_header_offset_candidate = [BitConverter]::ToInt32($unpacked_payload[($mz_offset_candidate+60)..($mz_offset_candidate+63)],0)
                    if($pe_header_offset_candidate -lt $unpacked_payload.Length -and ([char]$unpacked_payload[$mz_offset_candidate+$pe_header_offset_candidate] -eq 'P') -and ([char]$unpacked_payload[$mz_offset_candidate+$pe_header_offset_candidate+1] -eq 'E')){
                        Write-Output "[*] PE file found at offset $mz_offset_candidate"
                        Write-Output "[*] Unpacked content was prepended with $mz_offset_candidate bytes of junk data, removing..."
                        $unpacked_payload = $unpacked_payload[$mz_offset_candidate..$unpacked_payload.Length]
                        $output_path = $file_to_unpack + ".dll"
                        Set-Content $output_path -Value $unpacked_payload -Encoding Byte
                        Write-Output "[+] PE dumped to file $output_path"
                        $unpack_success = $true
                        break
                    }
                }
            }
        }
        # The extracted payload from powershell is a PIC, probably a Core Impact loader
        if(-not $unpack_success)
        {
            $pic_offset_string = ([int]$offset_start).ToString('X')
            Write-Output "[!] Unpacked content appears to be a packed Position Independent Code (PIC) starting at offset 0x$pic_offset_string, run Invoke-CoreImpactUnpacker to unpack it further" 
            $output_path = $file_to_unpack + ".pic"
            Set-Content $output_path -Value $unpacked_payload -Encoding Byte
            Write-Output "[+] Packed PIC extracted and dumped to file $output_path"
        } 
        # We remove all of the dinamically imported functions to avoid a SessionStateOverflowException FunctionOverflow exception at the next iteration
        $functions_all | ForEach-Object { $func_path="Function:\"+$_.Name; Remove-Item -Path $func_path -Force -ErrorAction SilentlyContinue}
    }
}

Оставьте комментарий