TITLE: PHP でバイナリプログラミング
http://events.php.gr.jp/events/show/96

#contents

* はじめに [#v18439f5]

PHP でバイナリ処理の話をあまり聞かないので、あえてニッチな所を狙って発表させて頂きます。

- キーワード: Binary、Format, Byte, Bit, Endian, Signed,
- デモ: JPEGからGPS抜き出し、Flash SWF 改変… (pure PHP で)

PHP というより、バイナリの勉強会といった要素が強いですが、
多分…、いつかお役に立てると思いますので、どうか、ご容赦ください。

* 一応、自己紹介 [#l413a7de]

- http://d.hatena.ne.jp/yoya/ でプログラミングしてて困った事とか書いてます。
- 一昨年位まで、数十万ユーザ規模の携帯サイトでアプリ開発をしていました。
--  その時に、PHP で主にバイナリ編集をしていました。フレームワークとかビジネスロジックとかよく知りません。
- 今も、一応 PHP でお仕事してます。あと、C 言語もたまに使います。

* まずは、バイナリの定義 [#vd6a4a1e]

- Wikipedia より
 通常バイナリとテキストは対比して用いられる。
 テキストとはデータの内容すべてを人間が読んで理解できる (human-readable) 表現形式を指し、
 バイナリとはそうでない表現形式を指すことが多い。

なので、本発表では、バイナリファイルの事を、
 テキストエディタで開いて読めない文字とか記号が表示されるようなファイル。
という事にしておきます。

* バイナリの実例 [#z1197160]

 % hexdump -C aria.gif
 00000000  47 49 46 38 39 61 c8 00  96 00 f7 00 00 00 00 00  |GIF89a..........|
 00000010  ff ff ff 96 53 58 29 1b  1c e6 b0 b8 b2 69 76 37  |....SX)......iv7|
 00000020  26 29 d6 96 a1 cb c6 c7  34 1c 22 48 31 38 2b 21  |&)......4."H18+!|
 <略>
GIF ファイルですね。
 $ hexdump -C kuriboo.png
 00000000  89 50 4e 47 0d 0a 1a 0a  00 00 00 0d 49 48 44 52  |.PNG........IHDR|
 00000010  00 00 00 c0 00 00 00 e0  08 06 00 00 00 55 70 69  |.............Upi|
 00000020  31 00 00 00 04 73 42 49  54 08 08 08 08 7c 08 64  |1....sBIT....|.d|
 <略>
PNG です。

先頭の4文字を見ると何か分かる事が多いのですが、その後ろにはよく分からないデータが続いてます。
このよく分からないデータを PHP で改変する方法について、今回はお話します。

* PHP とバイナリ [#e5d1b7d7]

PHP の string 型でバイナリ処理が簡単に出来る。というのが
今回、紹介する Tips の肝です。

(注) PHP6 では UTF-8 対応したり取りやめたりと怪しいので、今回の発表はとりあえず、PHP5(PHP4 も多分大丈夫) only での話しだと思ってください。

* 本当にバイナリ処理できるの? [#d76edda9]
** \0 は? [#ta2f5d12]

C言語が典型例ですが、\0 を文字列が終わったマークとして使う処理系も
結構あるので、string型をバイナリデータとして使う場合、途中で切れないか。
特に、C言語出身者は心配になると思います。

- 簡単に確認

 $s = "This is TEST\n";
 $s{3} = "\0";
 echo strlen($s)."\n";
 echo $s;

 13
 Thi is TEST

- 確認done

** 8bitスルー? [#e87fe524]

- US-ASCII は 7bit で表現できるので、先頭1bit が特別扱いされたりしない?

 $s = ' ';
 $s{3} = 'A';
 $s{4} = chr(ord('A') | 0x80);
 var_dump(bin2hex($s));

 string(10) "20202041c1"

- 8bit スルーです。日本語とかも入れられるし当然ですね。

** バイナリを取り込んでそのまま出力 [#q17ca2f5]

- でも、細かい事考えなくても、バイナリファイルを入力して、何も変えずに出力して同じデータが生成されれば、OKですよね。

 $data = file_get_contents($argv[1]);
 echo $data;

 % php echo.php saitama.jpg > output.dat
 % md5sum saitama.jpg output.dat
 06f741dca38937df3702f6759aead28b  saitama.jpg
 06f741dca38937df3702f6759aead28b  output.dat

- 入力をそのまま出力して、同じデータになりました。
- 後は、この途中でデータを分割したり結合したり、入れ替えたりすれば編集できる事になります。

* byte処理 [#y43196b3]

- String 関数が使えます http://php.net/manual/ja/ref.strings.php
--  strlen (データサイズを調べる。呼ぶたびに文字数を数えたりしないので安心)
--  substr (データから一部を抜き出す)
--  substr_replace (データの一部を入れ替える)
--  strrev (データの前後を逆にする) Endian 処理に便利

試してみましょう。

** JPEG の解析 [#pac8673a]

*** 初めの一歩 [#fb1c2d0c]

- まずファイルを開いてみる

 % hexdump -C aria.jpg
 00000000  ff d8 ff e0 00 10 4a 46  49 46 00 01 01 01 00 60  |......JFIF.....`|
 00000010  00 60 00 00 ff e1 00 22  45 78 69 66 00 00 49 49  |.`....."Exif..II|
 00000020  2a 00 08 00 00 00 01 00  00 51 04 00 01 00 00 00  |*........Q......|
 <略>

- 見てても法則が分からないので「JPEG フォーマット 仕様」でググってみる
-- http://siisise.net/jpeg.html#format

 マーカ・コード  	長さ  	データ
 FFxx(16bit) 	16bit 	可変サイズ
 
- ''16bit は 2 byte'' 、あと、ffXX で区切るらしい。

 00000000  ff d8 ff e0 00 10 ...
           ~~~~~ ~~~~~~~~~~~~~~~
           SOI   APP0
- 眺めていると、何となくピンと来るはず。

*** 分割してみる [#u317c98f]

- 分割できた


*** 欲しい情報を探してみる [#qbdaee18]

- 「JPEG GPS フォーマット 仕様」でググる
-- http://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q1217494967
- 「JPEG exif フォーマット 仕様」
--  http://www2.airnet.ne.jp/~kenshi/exif.html


* bit処理 [#mda8635b]

- 普通は pack を使うのですが… http://jp.php.net/manual/ja/function.pack.php
- 当たり前すぎるので、今回は使わない方法を。。


* その他Tips [#o4b9d5e0]

- PHP の閉じタグ ?> は使わない。?> の後ろに改行やゴミ文字があった場合に、テキストなら最後にゴミが付くだけで大きな問題になりにくいが、バイナリだと致命的。

Reload   Diff   Front page List of pages Search Recent changes Backup Referer   Help   RSS of recent changes