소개
파이썬에는 바이너리 데이터를 구문 분석하는 데 사용할 수 있는 라이브러리 Construct가 있습니다. 이 도구를 사용하여 네트워크 패킷, 형식화된 데이터 파일 등을 분석하는 것은 매우 편리합니다.
얼마 전에 sqlite 데이터베이스 파일 형식을 분석할 때 이 도구를 사용했다면 많은 수고를 덜 수 있었을 것입니다.
그러나 Construct 2.9는 이전 버전과 많이 달라졌습니다. Construct에서 작성한 원본 Python 코드는 기본적으로 다시 작성해야 합니다. Construct의 소스코드를 보는 과정에서 Construct의 기본 구현 아이디어가 재귀하강법 분석방법이라는 것을 알게 되었습니다. 객체의 생성 메소드를 사용하여 데이터 구조를 동적으로 정의하고 구문 분석 메소드에서 바이너리 데이터의 구문 분석을 구현합니다.
바이너리 데이터를 파싱하기 위해 PHP를 사용할 계획이지만 구현 아이디어가 Python의 Construct와 완전히 다릅니다.
추천 PHP 비디오 튜토리얼: https://www.php.cn/course/list/29/type/2.html
기본 아이디어
파이썬의 Construct는 파이썬의 객체 구문을 사용하여 동적 계층 구조를 구현하기 때문입니다. 정의 구문 분석은 Python의 객체 구문에 의해 제한되며 일부 구조 정의는 매우 모호해 보입니다.
내 생각은 동적 계층 구조를 설명하는 데 특별히 사용되는 작은 언어를 정의하여 사람들의 일반적인 표현 습관을 최대한 배려할 수 있도록 하는 것입니다.
이 소규모 프로젝트에는 C 언어의 구조 정의 구문을 기반으로 하고, 이를 기반으로 조건화 및 루프 구조 정의가 추가됩니다.
바이너리 데이터에는 공통 구조가 있습니다. 처음 몇 바이트는 후속 데이터 블록의 길이를 저장하고 그 뒤에 데이터 블록이 옵니다. 이런 구조를 C 언어의 구조 정의로는 표현하기가 불편합니다. 전체 데이터 블록의 길이는 다양하며 컴파일 타임에는 확인할 수 없으며 구문 분석 중에만 확인할 수 있습니다. 따라서 C 언어의 구조 정의 구문을 확장할 필요가 있습니다.
첫 번째 단계는 C 언어 구조 분석을 구현하는 것입니다
이 단계에서는 동적 계층 구조 정의를 고려하지 않지만 이 작은 언어의 핵심 부분을 구현합니다. C 언어의 구조 정의를 이해하고 이 구조 정의를 기반으로 바이너리 데이터를 구문 분석할 수 있습니다. 이 단계가 완료되면 동적 구조의 정의와 분석이 구현됩니다.
이 프로젝트는 이전 문서에서 간략하게 소개한 ADOS 스크립팅 언어 엔진을 기반으로 합니다.
먼저 작업을 살펴보겠습니다
완전히 C 언어 사양인 구조 정의 파일이 있습니다
blockStruct.h
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 )
感兴趣的朋友可以留意一下这三者之间的关系
下而是仅实现了对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语言的结构体进行编译,最终得到可以用于解析二进制数据的一系列函数,比如上述结构体定义文件中定义了两个结构体
struct student { char name[2]; int num; int age; char addr[3]; }; struct teacher { char name[2]; int num; char addr[3]; };
那么就会生成与这两个结构体对应的解析函数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 )희망 분석 결과를 얻으려면
rrreee
관심 있는 친구들은 세 가지의 관계에 주목하세요 다음은 C 언어 구조를 컴파일하는 어휘 규칙 파일뿐입니다rrreee다음은 C 언어를 컴파일하는 어휘 규칙 파일뿐입니다 구조체 컴파일을 위한 구문 규칙 파일 rrreee
다음은 기본 데이터 유형, 정수, 문자열에 대한 구문 분석 함수입니다. (참고, 실험에 사용되며 모든 기본 데이터 유형을 다루는 것은 아닙니다.)🎜rrreee🎜다음은 템플릿 교체에 사용됩니다. 작업 함수 🎜rrreee🎜이러한 준비가 완료되면 문법 안내 인코더를 구현하여 최종 구문 분석 기능을 생성할 수 있습니다. 🎜다음은 구조 정의를 구문적으로 분석할 때 바이너리 데이터를 구문 분석하는 데 사용할 수 있는 스크립트 코드를 생성하는 간단한 인코더입니다🎜rrreee 🎜자동으로 생성된 스크립트 파싱용 파일🎜🎜🎜은 C 언어 구조를 컴파일하고 최종적으로 바이너리 데이터를 파싱하는 데 사용할 수 있는 일련의 함수를 얻습니다. 예를 들어 위의 구조 정의 파일🎜rrreee에 두 개의 구조가 정의되어 있습니다. 🎜그러면 파싱 함수 parseStudent
및 parseTeacher
가 생성됩니다. 🎜🎜다음은 자동으로 생성된 테스트 스크립트입니다🎜rrreee🎜이 테스트 스크립트를 실행하면 결과는 다음과 같습니다.🎜rrreee🎜OK! 이제 작업의 첫 번째 단계가 완료되었습니다. 심판. 🎜🎜관련 질문이 더 필요하시면 PHP 중국어 웹사이트를 방문하세요: 🎜https://www.php.cn/🎜🎜
위 내용은 PHP는 Python의 Construct 라이브러리와 유사한 기능을 구현합니다. (1) 기본 디자인 아이디어의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!