はじめに
Pythonにはバイナリデータを解析するためのライブラリConstructがあり、ネットワークパケットやフォーマットされたデータファイルなどを解析する際にこのツールを利用すると非常に便利です。
少し前に sqlite データベース ファイル形式を分析するときにこのツールを使用していれば、多くの問題を避けられたでしょう。
ただし、Construct2.9 は以前のバージョンから大きく変更されており、基本的に Construct で書かれたオリジナルの Python コードを書き直す必要があります。 Construct のソースコードを見る過程で、Construct の基本的な実装アイデアは再帰降下解析手法であることがわかりました。オブジェクトの構築メソッドを使用してデータ構造を動的に定義し、parse メソッドでバイナリ データの解析を実装します。
php を使用してバイナリデータを解析する予定ですが、実装の考え方は Python の Construct とはまったく異なります。
推奨される PHP ビデオ チュートリアル: https://www.php.cn/course/list/29/type/2.html
基本的な考え方
Python の Construct は Python のオブジェクト構文を使用して動的階層構造の定義と解析を実装するため、Python のオブジェクト構文の制限により一部の構造定義が不明瞭に見えます。
私のアイデアは、動的な階層構造を記述するために特別に使用される小さな言語を定義し、人々の共通の表現習慣を可能な限り考慮できるようにすることです。
この小さなプロジェクトを例として、C 言語の構造定義構文に基づいて、条件付きとループ構造の定義を追加します。
バイナリ データには共通の構造があり、最初の数バイトには後続のデータ ブロックの長さが格納され、その後にデータ ブロックが続きます。このような構造をC言語の構造定義で表現するのは不便です。データ ブロック全体の長さは変化するため、コンパイル時には決定できませんが、解析中にのみ決定できます。そのため、C言語の構造定義構文を拡張する必要があります。
最初のステップは、C 言語構造の分析を実装することです
このステップでは、動的階層構造の定義は考慮しませんが、この小さな言語の核となる部分であるため、少なくとも C 言語の構造定義を理解でき、この構造定義に基づいてバイナリ データを解析できなければなりません。このステップが完了すると、動的構造の定義と分析が実装されます。
このプロジェクトは、前のファイルで簡単に紹介した ADOS スクリプト言語エンジンに基づいています。
まずタスクを見てみましょう
これは完全に C 言語仕様である構造定義ファイルです
blockStruct.h
struct student { char name[2]; int num; int age; char addr[3]; }; struct teacher { char name[2]; int num; char addr[3]; };
解析対象のバイナリ データ ブロック
"\x41\x42\x01\x00\x00\x00\x02\x00\x00\x00\x41\x42\x43\x41\x42\x01\x00\x00\x00\x41\x42\x43"
解析結果が得られることを願っています
[value] => Array ( [name] => AB [num] => 1 [age] => 2 [addr] => ABC ) [value] => Array ( [name] => AB [num] => 1 [addr] => ABC )
興味のある友人は、この 3 つの関係に注目してください
次の内容ですC 言語構造のコンパイルのみを実装する字句ルール ファイルです。
<?php /*! * struckwkr的词法规则 * 45022300@qq.com * Version 0.9.0 * * Copyright 2019, Zhu Hui * Released under the MIT license */ $GLOBALS['structwkr_lexRules'] = [ ['/^\"(.*?)\"/','_cons' ,'"'], ['/^\(/','_lp' ,'('], ['/^\)/','_rp' ,')'], ['/^\[/','_lb' ,'['], ['/^\]/','_rb' ,']'], ['/^\{/','_lcb' ,'{'], ['/^\}/','_rcb' ,'}'], ['/^;/','_semi' ,';'], ['/^,/','_comma' ,','], ['/^==/','_bieq' ,'='], ['/^!=/','_uneq' ,'!'], ['/^\>=/','_greq' ,'>'], ['/^\<=/','_leeq' ,'<'], ['/^=/','_equa' ,'='], ['/^\>/','_grea' ,'>'], ['/^\</','_less' ,'<'], ['/^\+/','_add' ,'+'], ['/^-/','_sub' ,'-'], ['/^\*/','_mul' ,'*'], ['/^\//','_div' ,'/'], ['/^%/','_mod' ,'$'], ['/^&&/','_and' ,'&'], ['/^\|\|/','_or' ,'|'], ['/^!/','_not' ,'!'], ['/^struct/','_strukey' ,'s'], ['/^char/','_char' ,'c'], ['/^int/','_int' ,'i'], ['/^float/','_float' ,'f'], ['/^double/','_double' ,'d'], ['/^[0-9]+([.]{1}[0-9]+){0,1}/','_num',''], ['/^\./','_dot' ,'.'], ['/^[\x{4e00}-\x{9fa5}A-Za-z_][\x{4e00}-\x{9fa5}A-Za-z0-9_]*\b/u','_iden',''], ['/^\s*/','_null',''] ];
以下は、C 言語構造のコンパイルのみを実装する文法ルール ファイルです。
<?php /*! * structwkr的语法规则处理器 * 45022300@qq.com * Version 0.9.0 * * Copyright 2019, Zhu Hui * Released under the MIT license */ namespace Ados; require_once 'const.php'; require_once __SCRIPTCORE__.'syntax_rule/base_rules_handler.php'; class StructwkrRulesHandler extends BaseRulesHandler{ //语法分析的起始记号,归约到最后得到就是若干个结构体定义的列表 function startToken(){ return '_structList'; } //附加类型 function extraType($extraArray){ if(count($extraArray)>0){ return $extraArray[0]; }else{ return ''; } } //求出放在附加信息中的数组长度 function elementSize($extraArray){ if(count($extraArray)>0){ return intval($extraArray[0]); }else{ return 0; } } //处理源代码中的多条声明语句 function handleStatementList($stack,$coder,$listIndex,$statementIndex){ $t1= $this->topItem($stack,$statementIndex); if($listIndex>0){ $t2= $this->topItem($stack,$listIndex); //将元素名所在项的附加信息数组找出来 $extraArray=$t2[TokenExtraIndex]; }else{ //$listIndex=0表示,statementList是上级节点,附加信息数组应该是空数组 $extraArray=[]; } //将statement中的附加信息添加到statementList的附加信息中去 $extraArray[]=$t1[TokenExtraIndex]; return ['#',$extraArray]; } //处理源代码中的一条声明语句 function handleStatement($stack,$coder,$typeItemIndex,$idenItemIndex){ $t1= $this->topItem($stack,$typeItemIndex); $elementType = $t1[TokenValueIndex]; $t2= $this->topItem($stack,$idenItemIndex); $elementName = $t2[TokenValueIndex]; //将元素名所在项的附加信息数组找出来 $extraArray=$t2[TokenExtraIndex]; $valLen =$extraArray[0]; //附加信息中包含 元素名称,元素类型,数据长度 return [$elementName,[$elementName,$elementType,$valLen]]; } //语法规则处理函数名由规则右边部分与规则左边部分拼接而成 //语法规则定义的先后决定了归约时匹配的顺序,要根据实际的语法安排 //如果不熟悉语法,随意调整语法规则的先后次序将有可能导致语法错误 // struct list {{{ function _structList_0_structList_struct($stack,$coder){ $coder->pushBlockTail(); return ['#',[]]; } function _structList_0_struct($stack,$coder){ $coder->pushBlockTail(); return ['#',[]]; } // struct list }}} // struct {{{ function _struct_0_structName_blockStatement_semi($stack,$coder){ $t1= $this->topItem($stack,3); $structName = $t1[TokenValueIndex]; $t2= $this->topItem($stack,2); $extraArray=$t2[TokenExtraIndex]; return [$structName,$extraArray]; } // struct }}} // struct name {{{ function _structName_0_strukey_iden($stack,$coder){ $t1= $this->topItem($stack,1); $structName = $t1[TokenValueIndex]; $coder->pushBlockHeader($structName); return $this->pass($stack,1); } // struct name }}} // blockStatement {{{ function _blockStatement_0_lcb_statementList_rcb($stack,$coder){ return $this->pass($stack,2); } // blockStatement }}} // statement list {{{ function _statementList_0_statementList_statement($stack,$coder){ return $this->handleStatementList($stack,$coder,2,1); } function _statementList_0_statement($stack,$coder){ //此处0表示statementList是上一级节点,要做特殊处理 return $this->handleStatementList($stack,$coder,0,1); } // statement list }}} // statement {{{ function _statement_0_double_term_semi($stack,$coder){ $t1= $this->topItem($stack,2); $elementName = $t1[TokenValueIndex]; $parseFuncName = 'parseDouble'; $coder->pushBlockBody($parseFuncName,$elementName); return $this->handleStatement($stack,$coder,3,2); } function _statement_0_float_term_semi($stack,$coder){ $t1= $this->topItem($stack,2); $elementName = $t1[TokenValueIndex]; $parseFuncName = 'parseFloat'; $coder->pushBlockBody($parseFuncName,$elementName); return $this->handleStatement($stack,$coder,3,2); } function _statement_0_char_term_semi($stack,$coder){ $t1= $this->topItem($stack,2); $elementName = $t1[TokenValueIndex]; $size = $this->elementSize($t1[TokenExtraIndex]); $parseFuncName = 'parseFixStr'; $coder->pushBlockBody($parseFuncName,$elementName,$size); return $this->handleStatement($stack,$coder,3,2); } function _statement_0_int_term_semi($stack,$coder){ $t1= $this->topItem($stack,2); $elementName = $t1[TokenValueIndex]; $parseFuncName = 'parseInt'; $coder->pushBlockBody($parseFuncName,$elementName,4); return $this->handleStatement($stack,$coder,3,2); } // statement }}} // term {{{ function _term_0_term_array($stack,$coder){ $t1= $this->topItem($stack,1); $valLen = $t1[TokenValueIndex]; //将数据长度放入附加信息 $t2= $this->topItem($stack,2); return [$t2[TokenValueIndex],[$valLen]]; } function _term_0_iden($stack,$coder){ $t1= $this->topItem($stack,1); //未指定数据长度时将长度值设为0 $valLen = '0'; $t2= $this->topItem($stack,2); return [$t1[TokenValueIndex],[$valLen]]; } // term }}} // array {{{ function _array_0_lb_num_rb($stack,$coder){ return $this->pass($stack,2); } // array }}} } // end of class
以下は、基本データです。型、整数、文字列の解析関数 (実験用に使用されており、すべての基本的なデータ型をまだカバーしていないことに注意してください)
<?php //用于解析基本数据类型的内置函数 namespace Ados; //解析一个字节,得到无符号整数值 function parseByte($context,$size = 0){ $pos=$context['pos']; $data = substr($context['data'], $pos); if(strlen($data)>0){ $raw = substr($data, 0,1); $value = unpack("C1",$raw)[1]; return ['value'=>$value,'size'=>1,'error'=>0,'msg'=>'ok']; }else{ return ['value'=>False,'size'=>0,'error'=>1,'msg'=>'not enough for parseByte']; } } //解析一个有符号整数 function parseInt($context,$size = 4){ if(!($size == 2 or $size == 4 or $size == 8 )){ return ['value'=>False,'size'=>0,'error'=>2,'msg'=>'not a valid size']; } if($size == 2) $format = "s1"; if($size == 4) $format = "l1"; if($size == 8) $format = "q1"; $pos=$context['pos']; $data = substr($context['data'], $pos); if(strlen($data)>=$size){ $raw = substr($data, 0,$size); $value = unpack($format,$raw)[1]; return ['value'=>$value,'size'=>$size,'error'=>0,'msg'=>'ok']; }else{ return ['value'=>False,'size'=>0,'error'=>1,'msg'=>'no data for parseInt']; } } //解析一个大端无符号整数 function parseBUInt($context,$size = 4){ if(!($size == 2 or $size == 4 or $size == 8 )){ return ['value'=>False,'size'=>0,'error'=>2,'msg'=>'not a valid size']; } if($size == 2) $format = "n1"; if($size == 4) $format = "N1"; if($size == 8) $format = "J1"; $pos=$context['pos']; $data = substr($context['data'], $pos); if(strlen($data)>=$size){ $raw = substr($data, 0,$size); $value = unpack($format,$raw)[1]; return ['value'=>$value,'size'=>$size,'error'=>0,'msg'=>'ok']; }else{ return ['value'=>False,'size'=>0,'error'=>1,'msg'=>'no data for parseBUInt']; } } //解析一个小端无符号整数 function parseLUInt($context,$size = 4){ if(!($size == 2 or $size == 4 or $size == 8 )){ return ['value'=>False,'size'=>0,'error'=>2,'msg'=>'not a valid size']; } if($size == 2) $format = "v1"; if($size == 4) $format = "NL"; if($size == 8) $format = "P1"; $pos=$context['pos']; $data = substr($context['data'], $pos); if(strlen($data)>=$size){ $raw = substr($data, 0,$size); $value = unpack($format,$raw)[1]; return ['value'=>$value,'size'=>$size,'error'=>0,'msg'=>'ok']; }else{ return ['value'=>False,'size'=>0,'error'=>1,'msg'=>'no data for parseLUInt']; } } //解析一个null结束的字符串 function parseString($context,$size=0){ $pos=$context['pos']; $data = substr($context['data'], $pos); $p=0; $raw = substr($data, $p,1); $byte = unpack("C1",$raw)[1]; $result =''; while($byte){ $result.=$raw; $p++; if($p>=strlen($data)){ return ['value'=>False,'size'=>0,'error'=>1,'msg'=>'not find null end for parseString']; } $raw = substr($data, $p,1); $byte = unpack("C1",$raw)[1]; } return ['value'=>$result,'size'=>$p,'error'=>0,'msg'=>'ok']; } //解析一个定长字符串 function parseFixStr($context,$size=0){ $pos=$context['pos']; $data = substr($context['data'], $pos); //var_dump($data); if(strlen($data)>=$size){ $result =''; for($i=0;$i<$size;$i++){ $raw = substr($data, $i,1); $value = unpack("a1",$raw)[1]; $result.=$value; } return ['value'=>$result,'size'=>$size,'error'=>0,'msg'=>'ok']; } return ['value'=>False,'size'=>0,'error'=>1,'msg'=>'not enough for parseFixedString']; }
以下はテンプレート置換用の機能する関数です
<?php //用于进行模板替换的工作函数 namespace Ados; defined('__STRUCT_PARSE_TEMP__') or define('__STRUCT_PARSE_TEMP__', './'); define('_blockParsTempFile_',__STRUCT_PARSE_TEMP__.'parseFunc.template.php'); //取出两个记号串之间的内容 function strBetweenToke($src,$toke1,$toke2){ $p1 = strpos($src,$toke1); $p2 = strpos($src,$toke2); return substr($src,$p1+strlen($toke1),$p2-$p1-strlen($toke1)); } //取得块分析函数的header function blockHeaderStr(){ $src = file_get_contents(_blockParsTempFile_); return strBetweenToke($src,'/*blockHeader{{*/','/*blockHeader}}*/'); } //取得块分析函数的body function blockBodyStr(){ $src = file_get_contents(_blockParsTempFile_); return strBetweenToke($src,'/*blockBody{{*/','/*blockBody}}*/'); } //取得块分析函数的tail function blockTailStr(){ $src = file_get_contents(_blockParsTempFile_); return strBetweenToke($src,'/*blockTail{{*/','/*blockTail}}*/'); } define('_blockHeaderStr_',blockHeaderStr()); define('_blockBodyStr_',blockBodyStr()); define('_blockTailStr_',blockTailStr()); function makeBlockHeader($blockName){ return str_replace('parseBlock_Temp', 'parse'.$blockName, _blockHeaderStr_); } function makeblockBody($parseFuncName,$filedName='',$filedSize=0){ $tmp = str_replace('parseByte', $parseFuncName, _blockBodyStr_); $tmp = str_replace('$filedName', $filedName, $tmp); $tmp = str_replace('$filedSize', $filedSize, $tmp); return $tmp; } function makeblockTail(){ return _blockTailStr_ ; } /* echo blockHeaderStr(); echo blockBodyStr(); echo blockTailStr(); echo makeBlockHeader('Test1'); echo makeblockBody('parseInt'); echo makeblockBody('parseStr'); echo makeblockTail(); */
その後これらの準備を行うと、文法ガイド付きエンコーダーを実装して、最終的な解析関数を生成できます。
次は、構造定義を構文的に分析するときにバイナリ データを解析するために使用できるスクリプト コードを生成する単純なエンコーダです。
<?php /*! * structwkr编码器, * * 45022300@qq.com * Version 0.9.0 * * Copyright 2019, Zhu Hui * Released under the MIT license */ namespace Ados; require_once __SCRIPTCORE__.'coder/base_coder.php'; require_once __STRUCT_PARSE_TEMP__.'templateReplaceFuncs.php'; class StructwkrCoder extends BaseCoder{ public function __construct($engine) { if($engine){ $this->engine = $engine; }else{ exit('the engine is not valid in StructwkrCoder construct.'); } } //编译得到的最终结果 public function codeLines(){ if(count($this->codeLines)<1){ return ''; } $script=''; for ($i=0;$i< count($this->codeLines);$i+=1) { $script.=$this->codeLines[$i]; } return $script; } //输出编译后的结果 public function printCodeLines(){ echo $this->codeLines(); } //添加一个块解析函数头 public function pushBlockHeader($structName){ $structName=ucfirst($structName); $content = makeBlockHeader($structName); array_push($this->codeLines, $content); $lineIndex=$this->lineIndex; $this->lineIndex+=1; return $lineIndex; } //添加一个块解析函数体 public function pushBlockBody($parseFuncName,$filedName='',$filedSize=0){ $content = makeblockBody($parseFuncName,$filedName,$filedSize); array_push($this->codeLines, $content); $lineIndex=$this->lineIndex; $this->lineIndex+=1; return $lineIndex; } //添加一个块解析函数尾 public function pushBlockTail(){ $content = makeblockTail(); array_push($this->codeLines, $content); $lineIndex=$this->lineIndex; $this->lineIndex+=1; return $lineIndex; } }
解析用に自動的に生成されるスクリプト ファイル
C 言語の構造をコンパイルし、最終的にバイナリ データの解析に使用できる一連の関数を取得します。たとえば、上記の構造定義ファイルには 2 つの構造が定義されています
struct student { char name[2]; int num; int age; char addr[3]; }; struct teacher { char name[2]; int num; char addr[3]; };
次に解析関数 #これら 2 つの構造に対応する ##parseStudent と
parseTeacher が生成されます。
False,'size'=>0,'error'=>$expRes['error'],'msg'=>$expRes['msg']]; } $expRes = parseInt($context,4); if($expRes['error']==0){ $filed = 'num'; if($filed){ $valueArray[$filed]=$expRes['value']; }else{ $valueArray[]=$expRes['value']; } $context['pos']+=$expRes['size']; $totalSize+= $expRes['size']; }else{ return ['value'=>False,'size'=>0,'error'=>$expRes['error'],'msg'=>$expRes['msg']]; } $expRes = parseInt($context,4); if($expRes['error']==0){ $filed = 'age'; if($filed){ $valueArray[$filed]=$expRes['value']; }else{ $valueArray[]=$expRes['value']; } $context['pos']+=$expRes['size']; $totalSize+= $expRes['size']; }else{ return ['value'=>False,'size'=>0,'error'=>$expRes['error'],'msg'=>$expRes['msg']]; } $expRes = parseFixStr($context,3); if($expRes['error']==0){ $filed = 'addr'; if($filed){ $valueArray[$filed]=$expRes['value']; }else{ $valueArray[]=$expRes['value']; } $context['pos']+=$expRes['size']; $totalSize+= $expRes['size']; }else{ return ['value'=>False,'size'=>0,'error'=>$expRes['error'],'msg'=>$expRes['msg']]; } return ['value'=>$valueArray,'size'=>$totalSize,'error'=>0,'msg'=>'ok']; } function parseTeacher($context,$size=0){ $valueArray=[]; $totalSize = 0; $expRes = parseFixStr($context,2); if($expRes['error']==0){ $filed = 'name'; if($filed){ $valueArray[$filed]=$expRes['value']; }else{ $valueArray[]=$expRes['value']; } $context['pos']+=$expRes['size']; $totalSize+= $expRes['size']; }else{ return ['value'=>False,'size'=>0,'error'=>$expRes['error'],'msg'=>$expRes['msg']]; } $expRes = parseInt($context,4); if($expRes['error']==0){ $filed = 'num'; if($filed){ $valueArray[$filed]=$expRes['value']; }else{ $valueArray[]=$expRes['value']; } $context['pos']+=$expRes['size']; $totalSize+= $expRes['size']; }else{ return ['value'=>False,'size'=>0,'error'=>$expRes['error'],'msg'=>$expRes['msg']]; } $expRes = parseFixStr($context,3); if($expRes['error']==0){ $filed = 'addr'; if($filed){ $valueArray[$filed]=$expRes['value']; }else{ $valueArray[]=$expRes['value']; } $context['pos']+=$expRes['size']; $totalSize+= $expRes['size']; }else{ return ['value'=>False,'size'=>0,'error'=>$expRes['error'],'msg'=>$expRes['msg']]; } return ['value'=>$valueArray,'size'=>$totalSize,'error'=>0,'msg'=>'ok']; }このテスト スクリプトを実行すると、結果は次のようになります:
Array ( [value] => Array ( [name] => AB [num] => 1 [age] => 2 [addr] => ABC ) [size] => 13 [error] => 0 [msg] => ok ) Array ( [value] => Array ( [name] => AB [num] => 1 [addr] => ABC ) [size] => 9 [error] => 0 [msg] => ok )OK! タスクの最初のステップは完了しました。を検討してから、条件判断による構造体の解析を実装することを検討してください。 その他の関連する質問については、PHP 中国語 Web サイトをご覧ください:
以上がPHPはPythonのConstructライブラリと同様の機能を実装します (1) 基本的な設計思想の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。