Skip to main content

PHP 操作二进制流,读取文件结构

· 12 min read

本文包含的内容:详细的pack和unpack调用详解,16进制数字字符串保存到文件,读取文件返回值,等

本文包含的内容:详细的pack和unpack调用详解,16进制数字字符串保存到文件,读取文件返回值,等包含实例:读取

BMP图片信息函数:

//将16进制字符串转换为此值的字符串。

function hexstr_str( $hetstr)

//两个版本,一个是pack实现,一个是chr方法实现

曾经为了处理源文件就百度了下php 2 进制文件,结果出来的貌似都是一个人写的,别人复制和转载的,而且我也偏偏没有看懂。挺郁闷的。因此就有了下文,我就自己搞打,研究研究。Php处理

2进制无非就是使用pack和unpack,在后文讲讲其他的办法。

函数原型:

string pack ( string format [, [mixed](http://cn2.php.net/manual/en/language.pseudo-types.php#language.types.mixed) args [, mixed $... ]] )

array unpack ( string format , string format , string data )

Pack是讲所给的参数更具所给的格式打包成2进制字符串。手册上说这个函数和Perl的基本相同,只是在格式上去掉了s,u等。这里有一点要注意的是,有符号和无符号的数由pack转换出来的结果相同,但是会影响unpack的结果。

先看一个小小的实例:

$data = pack("N", 0x12345);

var_dump($data);

$fn = 'out.text';

fp=fopen(fp = fopen(fn,'w');

fwrite(fp,fp,data);

结果很明了,用法很简单。

关于N模式:

unsigned long (always 32 bit, big endian byte order)

无符号长整型,总是返回32位

格式:

mode说嘛
a将字符串空白以 NULL 字符填满
A将字符串空白以 SPACE 字符 (空格) 填满
h十六进位字符串,低位在前
H十六进位字符串,高位在前
c有号字符
C无号字符
s有号短整数 (十六位,依计算机的位顺序)
S无号短整数 (十六位,依计算机的位顺序)
n无号短整数 (十六位, 高位在后的顺序)
v无号短整数 (十六位, 低位在后的顺序)
i有号整数 (依计算机的顺序及范围)
I无号整数 (依计算机的顺序及范围)
l有号长整数 (32位,依计算机的位顺序)
L无号长整数 (32位,依计算机的位顺序)
N无号短整数 (32位, 高位在后的顺序)
V无号短整数 (32位, 低位在后的顺序)
f单精确浮点数 (依计算机的范围)
d倍精确浮点数 (依计算机的范围)
x空位
X倒回一位
@填入 NULL 字符到绝对位置

关于pack的调用方法。

因为pack是支持多个不定个数参数的,所以每个参数都要指定一个转换的模式。

$data = pack("Nn", 0x12345, 0x12345);

当你参数的个数多于格式的个数时,出现:

Warning: pack() [function.pack]: 1 arguments unused in E:\host\htdocs\Lab\Binary.php on line 4

Warning级错误。

这个还有个注意的是*模式。

$data = pack("Nn*", 0x12345, 0x12345, 0x12345);\最后两个是16位,所以输出里面是2字节

var_dump($data);

$fn = 'out.text';

fp=fopen(fp = fopen(fn,'w');

fwrite(fp,fp,data);

输出:

看出来了最后两个是一样的。因为手册上没说,但是我猜测*模式就是“同上”的意思吧。

基础知识:

1字节 =  8位 = 2^8 =  256

一个gb2312字符2字节,一个ascii字符1字节。

关于模式的选择都由个人使用的情况决定,这里我给出我自己写的一个函数。

//将16进制字符串转换为此值的字符串。

function hexstr_str( $hetstr)

{

$re = '';

for( i=0; isset(i = 0; isset( hetstr[ i+4]);i + 4]); i += 4)

//echo substr( hetstr, hetstr, i, 2);

re.=pack(n, (0x.substr(re .= pack('n', ( '0x'.substr( hetstr, $i, 4)) * 1);

len=strlen(len = strlen( hetstr) - $i;

hetstr=0x.substr(hetstr = '0x'.substr( hetstr, i,strlen(i, strlen( hetstr) - $i);

hetstr.= (hetstr .= ( len % 2) ? '0':'';

format=format = len < 3 ? 'v': 'n';

re.=pack(re .= pack( format, $hetstr * 1);

return $re;

}

测试:

$data = hexstr_str( 'ABCDc');

输出:

有点遗憾的是它都要用0来补满16位。不过总的来说还是很不错的啦。

Unpack的使用,我觉得用着这个感觉挺揪心的。

unpack() works slightly different from Perl as the unpacked data is stored in an associative array. To accomplish this you have to name the different format codes and separate them by a slash /.

Unpack的运行和Perl的有些微不同,在php中unpack的结果是由一个数组返回的。因此你需要接受这些数据就要给格式命名,以/分隔。

手册上的实例;

<?php

array=unpack("c2chars/nint",array = unpack("c2chars/nint", binarydata);

?>

看一个我个人的调用实例:

$data = pack("n", ('0x'.'abc' ) * 1);

$data = hexstr_str( 'ABCDc');

s=s = data;

array=unpack("Nwen/n2stort",array = unpack("Nwen/n2stort", s.$s);

输出结果:

array(3) {

["wen"]=>

int(-1412579328)

["stort1"]=>

int(43981)

["stort2"]=>

int(49152)

}

上式结果也就是

wen:abcdc000

stort1:abcd

stort2:c000

看着这样的结果和输入的格式我觉得很揪心的。不过这里还是有点好处的,这个我们可以格式化输出一个文件的头信息什么的。

详细讲讲uppack的格式和输入的关系。

"Nwen/n2stort"

即第一个解析以N为格式,以wen为名字。

第二个解析以n为格式解析两次,以short为名字。

生成的short的名字是以short1,short2,……shortn这样的形式增长的。

解析的方法:

N,先在输入(s.s.s)中读入32位长,即4字节。然后再用N转换,此时指向s.s.s指针的位置已经到了4了,第一个s已经读完了,因此要是输入是s已经读完了,因此要是输入是s的话会报错:

Warning: unpack() [function.unpack]: Type n: not enough input, need 2, have 0 in E:\host\htdocs\Lab\Binary.php on line 7

bool(false)

此时的返回值为false。

在解析完第一个N后遇到了“/”进入下一组的解析,解析的格式为n,有2组,名字为short

所以short1为输入的第4-6解析的结果,而short2为6-8的结果。都是一一对应的。

这样看来也不是很难嘛是吧。

个人方法:

Perl思想中有一条是:

There's More Than One Way To Do It.

我也觉得如此。

Php中有个函数叫做

String chr( int $ascii )

返回一个字节,以ascii值为参数。

原本的ascii只有127的,但是后来扩展到了255加上0那就是256 很好 这样就都能表示了。

把上面那个函数稍微改造一下就是:

//将16进制字符串转换为此值的字符串。

function hexstr_str( $hexstr)

{

$re = '';

for( i=0; isset(i = 0; isset( hexstr[ i+1]);i +1]); i += 2)

re.=chr( (0x.re .= chr( ('0x'.hexstr[ i].i].hexstr[ $i + 1]) * 1);

if( strlen( hexstr) &gt; i)

re.=chr( (0x.re .= chr( ('0x'.hexstr[ $i].'0') * 1);

return $re;

}

代码简洁了很多啊。看起来顺眼多了。

置于我为啥研究这个玩意儿,我最开始也就是为了用啦读取BMP源文件的。

这里我们来简单写个读取BMP头信息的函数。

注意,这里是以24/32位图举例,16/8/2位位图在这里可能行不通的,因为数据结构不同。

BMP文件结构:

Begin

//文件头

section "BMP File Header"

read-only char[2] "BMP_ID" // 00

uint32 "File size" // 02

uint32 "Reserved" // 06

uint32  "ImageDataOffset" // 0A

endsection

//信息头

section "BMP Info Header"

uint32 "HeaderSize" // 0E

uint32 "Width" // 12

uint32 "Height" // 16

uint16 "Planes" // 1A

uint16 "BPP" // 1C

uint32 "CompessionMethod" // 1E

uint32 "ImageSize" // 22

uint32 "XPixelsPerMeter" // 26

uint32 "YPixelsPerMeter" // 2A

uint32 "PaletteSize" // 2E

uint32 "ColorsImportant" // 32

endsection

//调色板信息

section "Palette(If PaletteSize=0 then no palette)"

numbering 0

{

byte "B[~]"

byte "G[~]"

byte "R[~]"

byte "A[~]"

} [PaletteSize]

endsection

end

实现的代码如下:

$data = file_get_contents( 'Wener.bmp');

array=unpack("nBMPID/V文件大小/V保留字/V图片信息偏移/V头大小/V/V/vPlanes/vBPP/V压缩方法/V图片大小/V横轴上每像素宽/V竖轴上每像素宽/V调色板大小/V重要的颜色",array = unpack("nBMP_ID/V文件大小/V保留字/V图片信息偏移/V头大小/V宽/V高/vPlanes/vBPP/V压缩方法/V图片大小/V横轴上每像素宽/V竖轴上每像素宽/V调色板大小/V重要的颜色", data);

echo '<pre>';

var_dump( $array);

foreach( arrayasarray as k => $v)

echo k,:,(k,':',( v),'<br>';

请更改为你自己的图片地址后测试。我的输入如下:

BMP_ID:16973

文件大小:307254

保留字:0

图片信息偏移:54

头大小:40

宽:240

高:320

Planes:1

BPP:32

压缩方法:0

图片大小:307200

横轴上每像素宽:0

竖轴上每像素宽:0

调色板大小:0

重要的颜色:0

我的测试文件:

总结:

总的,来说,php的这两个函数无疑是很强大的。用来分析文件也很好用。

或许不足的就是缺少这方面的案例吧,没见到很多人用过这个。

以后自己学习文件的结构也会常常用到这个函数,有时候觉得unpack比pack更有魅力。关于更详细的BMP文件结构和信息的获取我会另外写一篇文章的。这篇只是略微的涉及,作为一个例子而已。我还是觉得

Php处理2进制流的魅力是非常强大的。

置于建立在这个之上写更多的运用(加密,协议,破解,获取文件信息)什么的就看个人发挥了。