隐写术——PNG文件隐藏payload

0x01 PNG文件格式

PNG文件基本上由两部分组成:
文件头、文件数据块
文件头也叫署名域
用来标识这是一个PNG格式的文件,8字节长度,固定数据:89 50 4E 47 0D 0A 1A 0A
数据块:
PNG定义了两种类型的数据块:
1:关键数据块(Critical Chunk):PNG文件必须包含,读写软件也必须支持的数据块
2:辅助数据块(Ancillary Chunk):PNG允许软件忽略不认识的数据,辅助数据块的设计允许PNG格式在扩展的时候仍然能够保证与旧版本相兼容
关键数据块:Critical Chunk
包含4种类型:
1:IHDR(header chunk):文件头数据块,包含图像基本信息,作为第一个数据块出现并只出现一次
2:PLTE(palette chunk):调色板数据块,必须存放在图像数据快之前
3:IDAT(image data chunk):图像数据块,存储实际的图像数据,PNG数据包允许包含多个连续的图像数据快
4:IEND(image trailer chunk):图像结束数据块,标识PNG数据流结束
辅助数据块:Ancillary Chunk
PNG文件格式规范了10个辅助数据块
1:背景颜色数据块bKGD——background color
2:基色和白色度数据块cHRM——primary chromaticities and white point
3:图像γ数据块gAMA——image gamma
4:图像直方图数据块hIST——image histogram
5:物理像素尺寸数据块pHYs——physical pixel dimensions
6:样本有效位数据块sBIT——significant bits
7:文本信息数据块tEXt——textual data
8:图像最后修改时间数据块tIME ——image last-modification time
9:图像透明数据块tRNS ——transparency
10:压缩文本数据块zTXt ——compressed textual data
数据块结构
PNG的每个数据块都由一下四个部分组成:
长度Length 4byte 制定数据块中数据域的长度,长度不超过2^31-1字节
数据块类型码Chunk Type Code 4byte 由ASCII码字母组成
数据块数据Chunk Data Length bytes 存储按照Chunk Type Code指定的数据
循环冗余检测 CRC 4bytes 存储用来检测是否错误的循环冗余码
IHDR数据块结构:(Header chunk)
由13个字节组成,格式如下:
Width 4bytes 图像宽度,单位:像素
Height 4bytes 图像长度,单位:像素
Bit depth 1byte 图像深度
ColorType 1byte 颜色类型
Compression method 1byte 压缩方法
Filter method 1byte 滤波器方法
Interlace method 1byte 隔行扫描方法
调色板数据块PLTE(palette chunk)
Red 1byte 0=黑;255=红
Green 1byte 0=黑;255=绿
Blue 1byte 0=黑;255=蓝
图像数据快(IDAT)
存数世纪的数据,在数据流中允许包含多个连续顺序的图像数据快
图像数据结束块(IEND)
必须在文件尾部,标志着PNG数据流结束

0x02实例文件解析

隐写术——PNG文件隐藏payload_第1张图片
格式解析:

  1. 文件头(文件署名域):固定格式: 89 50 4E 47 0D 0A 1A 0A
  2. 数据块
    2.1 IHDR
    00 00 00 0D 49 48 44 52 00 00 0F E8 00 00 1F 40 08 06 00 00 00 12 88 23 1A
    
    结构解析:
    Legth :前4个字节:00 00 00 0D
    Chunk Type Code(数据块类型码):4字节 49 48 44 52
    Chunk Data:存储按照Chunk Type Code指定的数据,长度等于Legth
    CRC: 4字节,对Chunk Type Code+Chunk Data作CRC32计算得出的值
    计算数据:49 48 44 52 00 00 0F E8 00 00 1F 40 08 06 00 00 00
    这里CRC用来校验前边的数据是否存在错误
    2.2 tExt
    00 00 00 19 74 45 58 74 53 6F 66 74 77 61 72 65 00 41 64 6F 62 65 20 49 6D 61 67 65 52 65 61 64 79 71 C9 65 3C
    Length :00 00 00 19
    Chunk Type Code(数据块类型码):4字节 74 45 58 74
    Chunk Data:存储按照Chunk Type Code指定的数据,长度等于Legth(25)
    CRC: 4字节,对Chunk Type Code+Chunk Data作CRC32计算得出的值
    2.3 IDAT
    Length :00 60 45 62
    Chunk Type Code(数据块类型码):4字节 49 44 41 54
    Chunk Data:存储按照Chunk Type Code指定的数据,长度等于Legth(25)
    CRC: 4字节,对Chunk Type Code+Chunk Data作CRC32计算得出的值
    2.4 IEND
    Length :00 00 00 00
    Chunk Type Code(数据块类型码):4字节 49 45 4E 44
    Chunk Data:存储按照Chunk Type Code指定的数据,长度等于Legth(25)
    CRC: 4字节,对Chunk Type Code+Chunk Data作CRC32计算得出的值

0x03自动化解析文件格式

#include
#include
int main(int argc, char* argv[])
{
	const char* destFile;
	/*if (argc < 2)
	{
		printf("useage: CheckPng.exe FilePath");
		return 0;
	}
	else
	{
	}*/
	long FileSize;
	destFile = "pngsucai_607406_c3c768.png";//argv[1];
	FILE* fp = fopen(destFile, "rb");					// 尝试读取(r)一个二进制(b)文件,成功则返回一个指向文件结构的指针,失败返回空指针
	if (fp == NULL)
	{
		printf("文件读取失败!\n");
	}
	fseek(fp, 0, SEEK_END);							// 设置文件流指针指向PE文件的结尾处
	FileSize = ftell(fp);							// 得到文件流指针当前位置相对于文件头部的偏移字节数,即获取到了PNG文件大小(字节)

	char* buf = new char[FileSize];				// 新建一个数组指针buf,指向一个以PNG文件字节数作为大小的数组
	memset(buf, 0, FileSize);						// buf指针指向内存中数组的开始位置
													// 在这里用0初始化一块FileSize大小的内存,即为数组分配内存
	fseek(fp, 0, SEEK_SET);							// 将文件流指针指向PNG文件头部
	fread(buf, 8, 1, fp);					// 读取前4个字节的内容,是PNG文件的魔数
		printf("========================================================\n");
		printf("文件大小:%d\n", FileSize);
		printf("========================================================\n");
		//开始解析文件结构
		//开始8个字节是PNG文件的文件署名域		
		printf("=======================文件数据块========================\n");
		int ChunkOffset = 8;
		unsigned int ChunkLen = 0;
		unsigned int ChunkCRC32 = 0;
		int chunknumber = 0;
		while (1)
		{
			chunknumber++;
			fread(buf, 4, 1, fp);//读取第一个数据块的前4字节的数据
			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| strncmp((char*)buf, "IENF", 4) == 0)
			{
				printf("Critical Chunk\n");
			}
			else
			{
				printf("Ancillary Chunk\n");
			}
			printf("ChunkOffset:0x%08x	\n", ChunkOffset);//输出数据块的偏移值
			printf("ChunkLength:%d      \n", ChunkLen);//输出对应数据块的长度
			ChunkOffset += ChunkLen + 0xC;             //调整偏移到下一个数据块,因为Chunk Type Code的长度为12
			fread(buf, 4, 1, fp);//继续读取4字节数据,这个是crc32的数据
			ChunkCRC32 = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
			printf("ChunkCRC32: %08X		\n", ChunkCRC32);//输出CRC32校验码
			ChunkLen = ftell(fp);//已经读取过数据的大小
			if (ChunkLen == FileSize )  //最后一个数据块的大小固定为12个字节
			{
				printf("\n----------------------------------------------------\n");
				printf("Total ChunkNumber:%d\n", chunknumber);
				break;
			}
		}
			
	

}

0x04手动添加payload

原始文件结构如下:
隐写术——PNG文件隐藏payload_第2张图片
手动删除辅助数据之后依然可以正常打开文件
隐写术——PNG文件隐藏payload_第3张图片
隐写术——PNG文件隐藏payload_第4张图片
接下来手动添加payload进去:
添加payload需要注意的几个点
1:加入和删除的数据必须是辅助数据的内容
2:修改内容:数据长度、对应长度的payload
3:修改对应的CRC32的校验码(对对Chunk Type Code+Chunk Data作CRC32计算得出的值)
此处示例payload为calc.exe,修改的数据如下:

Length:				00 00 00 08                    payload的长度
Chunk Type Code:	74 45 58 74            数据块类型tEXt
Chunk Data:			63 61 6c 63 2e 65 78 65     对应的payload数据
CRC:				fa c4 08 76                         对应的CRC32校验码

添加payload数据:
隐写术——PNG文件隐藏payload_第5张图片
大功告成,接下来就可以以其他方式来读取文件内容获取payload

你可能感兴趣的:(隐写术,隐写术,PNG文件格式)