标题: MISC系列(51)--010 Editor模板编写入门 创建: 2021-03-21 18:20 更新: 2023-05-17 13:11 链接: https://scz.617.cn/misc/202103211820.txt https://www.52pojie.cn/thread-1398493-1-1.html https://www.52pojie.cn/thread-1402549-1-1.html -------------------------------------------------------------------------- 目录: ☆ 背景介绍 ☆ SerializeTest.py 1) 序列化输出结构 2) SerializeTest_1.py ☆ SerializeTest.bt 1) 元素为变长结构的结构数组 2) SerializeTest_1.bt ☆ QA 1) 向Output窗口输出内容 2) 3) 4) 5) 将字符串转换成整数 6) 从hexdump中反向定位模板字段 ☆ 进阶问题 1) 将一批整数映射到字符串 2) 显示格式化过的时间字符串 3) 4) 结构优化对齐 5) 变长结构中的变长成员变量 6) 元素为变长结构的结构数组 7) 变量的作用域 8) 字符串拼接 9) parentof()/startof() ☆ Construct vs 010 Editor ☆ 参考资源 -------------------------------------------------------------------------- ☆ 背景介绍 上周在看Zend VM OPcache,它的bin文件格式是版本强相关的,随PHP版本不同需要 不同的解析方式。010 Editor提供的模板不适用于我当时看的版本。起初我在原模板 基础上小修小改对付着用,后来发现需要修改的地方比较多,也不太适应原作者的解 析思路,就打算自己写一个匹配版本的解析模板。 从未完整写过010 Editor模板,直接解析Zend VM OPcache的bin,会碰上很多与目标 不直接相关的低级问题,比如基础语法、功能函数等等。决定先写一个Python脚本, 对一组稍显复杂的自定义数据结构进行自定义序列化输出,然后写010 Editor模板解 析前述序列化数据,以此掌握010 Editor模板基础语法、功能函数。 ☆ SerializeTest.py -------------------------------------------------------------------------- #! /usr/bin/env python # -*- encoding: utf-8 -*- import struct import hexdump # ######################################################################### # # # little-endian # def i2b_8_l ( n ) : return( struct.pack( ' len( buf ) ) : return( None ) array = [] for i in range( count ) : j = b2i_32_l( buf[4+i*4:4+i*4+4] ) tmp = cls.Deserialize( buf[j:] ) if ( tmp is None ) : return( None ) array.append( tmp ) return( array ) # # end of DeserializeArray # # ######################################################################### # # # struct _zend_string { # uint32_t id; # size_t len; # char val[1]; # } # class MyString () : def __init__ ( self, id, sth ) : self.id = id self.sth = sth # # end of __init__ # def __repr__ ( self ) : return( "MyString [%u:%s]" % ( self.id, self.sth ) ) # # end of __repr__ # def show ( self ) : print( "MyString [%u:%s]" % ( self.id, self.sth ) ) # # end of show # # # 返回bytes # def Serialize ( self ) : b = s2b( self.sth ) return( i2b_32_l( self.id ) + i2b_32_l( len( b ) ) + b ) # # end of Serialize # @staticmethod def Deserialize ( buf ) : if ( len( buf ) < 8 ) : return( None ) id = b2i_32_l( buf[0:4] ) i = b2i_32_l( buf[4:4+4] ) if ( 8 + i > len( buf ) ) : return( None ) sth = b2s( buf[8:8+i] ) return( MyString( id, sth ) ) # # end of Deserialize # # # # # count + off_array + buf_array # # # @staticmethod # def SerializeArray ( array ) : # count = len( array ) # off = b'' # buf = b'' # j = 4 + 4 * count # for i in range( count ) : # off += i2b_32_l( j ) # tmp = array[i].Serialize() # buf += tmp # j += len( tmp ) # return( i2b_32_l( count ) + off + buf ) # # # # end of SerializeArray # # # # # # count + off_array + buf_array # # # @staticmethod # def DeserializeArray ( buf ) : # if ( len( buf ) < 4 ) : # return( None ) # count = b2i_32_l( buf[0:4] ) # k = 4 + 4 * count # if ( k > len( buf ) ) : # return( None ) # array = [] # for i in range( count ) : # j = b2i_32_l( buf[4+i*4:4+i*4+4] ) # if ( k != j ) : # return( None ) # tmp = MyString.Deserialize( buf[j:] ) # if ( tmp is None ) ; # return( None ) # array.append( tmp ) # k += len( tmp ) # return( array ) # # # # end of DeserializeArray # # # # end of MyString # # ######################################################################### # # # struct _zval_struct { # zend_value value; # union { # uint32_t type_info; # } u1; # union { # uint32_t lineno; # } u2; # } # class MyZval () : def __init__ ( self, value, type_info, lineno ) : # # 64位整数 # self.value = value # # 32位整数 # self.type_info = type_info self.lineno = lineno # # end of __init__ # def __repr__ ( self ) : return( "MyZval [%#x:%u:%u]" % ( self.value, self.type_info, self.lineno ) ) # # end of __repr__ # def show ( self ) : print( "MyZval [%#x:%u:%u]" % ( self.value, self.type_info, self.lineno ) ) # # end of show # # # 返回bytes # def Serialize ( self ) : return( i2b_64_l( self.value ) + i2b_32_l( self.type_info ) + i2b_32_l( self.lineno ) ) # # end of Serialize # @staticmethod def Deserialize ( buf ) : if ( len( buf ) < 16 ) : return( None ) value = b2i_64_l( buf[0:8] ) type_info = b2i_32_l( buf[8:8+4] ) lineno = b2i_32_l( buf[12:12+4] ) return( MyZval( value, type_info, lineno ) ) # # end of Deserialize # # # end of MyZval # # ######################################################################### # # # struct _zend_op { # znode_op op1; # znode_op op2; # znode_op result; # uint32_t lineno; # zend_uchar opcode; # zend_uchar op1_type; # zend_uchar op2_type; # zend_uchar result_type; # } # class MyZendOp () : def __init__ ( self, op1, op2, result, lineno, opcode, op1_type, op2_type, result_type ) : # # 32位整数 # self.op1 = op1 self.op2 = op2 self.result = result self.lineno = lineno # # unsigned char # self.opcode = opcode self.op1_type = op1_type self.op2_type = op2_type self.result_type = result_type # # end of __init__ # def __repr__ ( self ) : return( "MyZendOp [%#x:%#x:%#x (%u)]" % ( self.opcode, self.op1, self.op2, self.lineno ) ) # # end of __repr__ # def show ( self ) : print( "MyZendOp [%#x:%#x:%#x (%u)]" % ( self.opcode, self.op1, self.op2, self.lineno ) ) # # end of show # # # 返回bytes # def Serialize ( self ) : return \ ( i2b_32_l( self.op1 ) + i2b_32_l( self.op2 ) + i2b_32_l( self.result ) + i2b_32_l( self.lineno ) + i2b_8_l( self.opcode ) + i2b_8_l( self.op1_type ) + i2b_8_l( self.op2_type ) + i2b_8_l( self.result_type ) ) # # end of Serialize # @staticmethod def Deserialize ( buf ) : if ( len( buf ) < 20 ) : return( None ) op1 = b2i_32_l( buf[0:4] ) op2 = b2i_32_l( buf[4:4+4] ) result = b2i_32_l( buf[8:8+4] ) lineno = b2i_32_l( buf[12:12+4] ) # opcode = b2i_8_l( buf[16:16+1] ) # op1_type = b2i_8_l( buf[17:17+1] ) # op2_type = b2i_8_l( buf[18:18+1] ) # result_type = b2i_8_l( buf[19:19+1] ) opcode = buf[16] op1_type = buf[17] op2_type = buf[18] result_type = buf[19] return( MyZendOp( op1, op2, result, lineno, opcode, op1_type, op2_type, result_type ) ) # # end of Deserialize # # # end of MyZendOp # # ######################################################################### # # # struct _zend_op_array { # zend_uchar type; # uint32_t last; # zend_op *opcodes; # int last_var; # zend_string **vars; # zend_string *filename; # int last_literal; # zval *literals; # } # class MyZendOpArray () : def __init__ ( self, type, opcodes, vars, filename, literals ) : self.type = type # # MyZendOp[] # self.opcodes = opcodes # # MyString[] # self.vars = vars self.filename = filename # # MyZval[] # self.literals = literals # # end of __init__ # def __repr__ ( self ) : return( "MyZendOpArray [%u:%s]" % ( self.type, self.filename ) ) # # end of __repr__ # def show ( self ) : print( "MyZendOpArray [%u:%s]" % ( self.type, self.filename ) ) for i in range( len( self.opcodes ) ) : print( self.opcodes[i] ) for i in range( len( self.vars ) ) : print( self.vars[i] ) for i in range( len( self.literals ) ) : print( self.literals[i] ) # # end of show # # # 返回bytes # # 只是一种演示方案,不是真实案例,有些字段安排甚至说不上合理,源自Zend # VM OPcache, # def Serialize ( self ) : buf = i2b_8_l( self.type ) last = len( self.opcodes ) buf += i2b_32_l( last ) opcodes_index = len( buf ) # # 占位 # buf += i2b_32_l( 0 ) opcodes_buf = SerializeArray( self.opcodes )[4:] last_var = len( self.vars ) buf += i2b_32_l( last_var ) vars_index = len( buf ) # # 占位 # buf += i2b_32_l( 0 ) vars_buf = SerializeArray( self.vars )[4:] filename_index = len( buf ) # # 占位 # buf += i2b_32_l( 0 ) filename_buf = self.filename.Serialize() last_literal = len( self.literals ) buf += i2b_32_l( last_literal ) literals_index = len( buf ) # # 占位 # buf += i2b_32_l( 0 ) literals_buf = SerializeArray( self.literals )[4:] buf = PatchBytes( buf, opcodes_index, 4, i2b_32_l( len( buf ) ) ) buf += opcodes_buf buf = PatchBytes( buf, vars_index, 4, i2b_32_l( len( buf ) ) ) buf += vars_buf buf = PatchBytes( buf, filename_index, 4, i2b_32_l( len( buf ) ) ) buf += filename_buf buf = PatchBytes( buf, literals_index, 4, i2b_32_l( len( buf ) ) ) buf += literals_buf return( buf ) # # end of Serialize # @staticmethod def Deserialize ( buf ) : if ( len( buf ) < 29 ) : return( None ) type = b2i_8_l( buf[0:1] ) last = b2i_32_l( buf[1:1+4] ) opcodes_off = b2i_32_l( buf[5:5+4] ) opcodes = DeserializeArray( i2b_32_l( last ) + buf[opcodes_off:], MyZendOp ) if ( opcodes is None ) : return( None ) last_var = b2i_32_l( buf[9:9+4] ) vars_off = b2i_32_l( buf[13:13+4] ) vars = DeserializeArray( i2b_32_l( last_var ) + buf[vars_off:], MyString ) if ( vars is None ) : return( None ) filename_off = b2i_32_l( buf[17:17+4] ) filename = MyString.Deserialize( buf[filename_off:] ) if ( filename is None ) : return( None ) last_literal = b2i_32_l( buf[21:21+4] ) literals_off = b2i_32_l( buf[25:25+4] ) literals = DeserializeArray( i2b_32_l( last_literal ) + buf[literals_off:], MyZval ) if ( literals is None ) : return( None ) return( MyZendOpArray( type, opcodes, vars, filename, literals ) ) # # end of Deserialize # # # end of MyZendOpArray # # ######################################################################### # # mystr = MyString( 0, "Test" ) # mystr.show() # buf = mystr.Serialize() # hexdump.hexdump( buf ) # mystr = MyString.Deserialize( buf ) # mystr.show() # # myzval = MyZval( 0xffffffff00112233, 6, 12 ) # myzval.show() # buf = myzval.Serialize() # hexdump.hexdump( buf ) # myzval = MyZval.Deserialize( buf ) # myzval.show() # # myzendop = MyZendOp \ # ( # 1, 2, 3, 4, 5, 6, 7, 8 # ) # myzendop.show() # buf = myzendop.Serialize() # hexdump.hexdump( buf ) # myzendop = MyZendOp.Deserialize( buf ) # myzendop.show() type = 0x41 opcodes = \ [ MyZendOp( 1, 2, 3, 4, 5, 6, 7, 8 ), MyZendOp( 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38 ) ] vars = \ [ MyString( 0, "Test_0" ), MyString( 1, "Test_1" ) ] filename = MyString( 2, "Test_2.php" ) literals = \ [ MyZval( 0xffffffff00112233, 6, 12 ), MyZval( 0xfefefefe44556677, 7, 13 ) ] myzendoparray \ = MyZendOpArray( type, opcodes, vars, filename, literals ) myzendoparray.show() buf = myzendoparray.Serialize() hexdump.hexdump( buf ) WriteBytes( 'SerializeTest.bin', buf ) myzendoparray \ = MyZendOpArray.Deserialize( buf ) # print( myzendoparray ) myzendoparray.show() -------------------------------------------------------------------------- $ python3 SerializeTest.py MyZendOpArray [65:MyString [2:Test_2.php]] MyZendOp [0x5:0x1:0x2 (4)] MyZendOp [0x35:0x31:0x32 (52)] MyString [0:Test_0] MyString [1:Test_1] MyZval [0xffffffff00112233:6:12] MyZval [0xfefefefe44556677:7:13] 00000000: 41 02 00 00 00 1D 00 00 00 02 00 00 00 4D 00 00 A............M.. 00000010: 00 71 00 00 00 02 00 00 00 83 00 00 00 0C 00 00 .q.............. 00000020: 00 20 00 00 00 01 00 00 00 02 00 00 00 03 00 00 . .............. 00000030: 00 04 00 00 00 05 06 07 08 31 00 00 00 32 00 00 .........1...2.. 00000040: 00 33 00 00 00 34 00 00 00 35 36 37 38 0C 00 00 .3...4...5678... 00000050: 00 1A 00 00 00 00 00 00 00 06 00 00 00 54 65 73 .............Tes 00000060: 74 5F 30 01 00 00 00 06 00 00 00 54 65 73 74 5F t_0........Test_ 00000070: 31 02 00 00 00 0A 00 00 00 54 65 73 74 5F 32 2E 1........Test_2. 00000080: 70 68 70 0C 00 00 00 1C 00 00 00 33 22 11 00 FF php........3"... 00000090: FF FF FF 06 00 00 00 0C 00 00 00 77 66 55 44 FE ...........wfUD. 000000A0: FE FE FE 07 00 00 00 0D 00 00 00 ........... MyZendOpArray [65:MyString [2:Test_2.php]] MyZendOp [0x5:0x1:0x2 (4)] MyZendOp [0x35:0x31:0x32 (52)] MyString [0:Test_0] MyString [1:Test_1] MyZval [0xffffffff00112233:6:12] MyZval [0xfefefefe44556677:7:13] 1) 序列化输出结构 直接看SerializeTest.py的实现就能明白序列化方案,下面简介一二。 SerializeTest.py在序列化输出这个结构: struct _zend_op_array { zend_uchar type; uint32_t last; zend_op *opcodes; // opcodes[last] int last_var; zend_string **vars; // vars[last_var] zend_string *filename; int last_literal; zval *literals; // literals[last_literal] } 这是个简化版结构,但足够复杂。其中vars[]本来是指针数组,但对序列化而言,当 成zend_string[]序列化出去没啥不同,毕竟在序列化过程中指针都得换个方式处理。 下面这个结构将所有指针换成some_off的形式: struct _zend_op_array { zend_uchar type; uint32_t last; uint32_t opcodes_off; // opcodes[last] int last_var; uint32_t vars_off; // vars[last_var] uint32_t filename_off; // filename int last_literal; uint32_t literals_off; // literals[last_literal] } 将来序列化输出的格式大致如下: head + opcodes[] + vars[] + filename + literals[] head就是上面那个结构,其中的some_off分别对应opcodes[]、vars[]、filename、 literals[]的偏移,基址是head的首字节。 序列化输出中的some[]不是简单的结构数组,而是结构数组的序列化输出。本例中 some[]的格式大致如下: off[] + buf[] off[]是一个偏移数组,靠off[i]定位buf[i]。buf[i]是单个some结构的序列化输出。 在SerializeTest.py的实现中,off[i]的基址并不是head的首字节。 参看SerializeTest.py中SerializeArray()、DeserializeArray()的实现。 SerializeArray()本来输出的是: count + off[] + buf[] off[i]的基址是count的首字节。但some[]不包含count,count被放到head中去了。 为啥会这样?本文参考了Zend VM OPcache的实现,在简化结构的同时想展示一些复 杂之处,保留了某些原设定。 无论如何,这只是一种自定义序列化方案,仅作演示,没啥道理可言。 2) SerializeTest_1.py SerializeTest_1.py用到@classmethod装饰器,没有本质变化。为了演示变长结构数 组,这样声明vars[]: vars = \ [ MyString( 0, "Test_0" ), MyString( 1, "Test_1_1" ), MyString( 2, "Test_2_2_2" ), ] 生成SerializeTest_1.bin,将来用SerializeTest_1.bt解析。 ☆ SerializeTest.bt $ xxd -g 1 SerializeTest.bin 00000000: 41 02 00 00 00 1d 00 00 00 02 00 00 00 4d 00 00 A............M.. 00000010: 00 71 00 00 00 02 00 00 00 83 00 00 00 0c 00 00 .q.............. 00000020: 00 20 00 00 00 01 00 00 00 02 00 00 00 03 00 00 . .............. 00000030: 00 04 00 00 00 05 06 07 08 31 00 00 00 32 00 00 .........1...2.. 00000040: 00 33 00 00 00 34 00 00 00 35 36 37 38 0c 00 00 .3...4...5678... 00000050: 00 1a 00 00 00 00 00 00 00 06 00 00 00 54 65 73 .............Tes 00000060: 74 5f 30 01 00 00 00 06 00 00 00 54 65 73 74 5f t_0........Test_ 00000070: 31 02 00 00 00 0a 00 00 00 54 65 73 74 5f 32 2e 1........Test_2. 00000080: 70 68 70 0c 00 00 00 1c 00 00 00 33 22 11 00 ff php........3"... 00000090: ff ff ff 06 00 00 00 0c 00 00 00 77 66 55 44 fe ...........wfUD. 000000a0: fe fe fe 07 00 00 00 0d 00 00 00 ........... SerializeTest.bt是用于解析SerializeTest.bin的010 Editor模板。 -------------------------------------------------------------------------- // // struct _zend_string { // uint32_t id; // size_t len; // char val[1]; // } // typedef struct _MyString { uint id; uint len; // // 自动用前面的len成员定义此处的char[] // char val[len]; // // Read functions can also be used to show information beside a struct // without having to open the struct in the Template Results. When // using read functions with a struct, the read function receives a // reference to the struct and the '&' symbol should be used when // declaring the parameter. // // read回调会影响GUI中Value列的显示,只是增强显示效果,可以不提供 // } MyString ; string MyStringRepr ( MyString &obj ) { if ( 0 == obj.len ) { return( "(null)" ); } return( obj.val ); } // ////////////////////////////////////////////////////////////////////////// // // // struct _zend_op { // znode_op op1; // znode_op op2; // znode_op result; // uint32_t lineno; // zend_uchar opcode; // zend_uchar op1_type; // zend_uchar op2_type; // zend_uchar result_type; // } // typedef struct _MyZendOp { uint op1; uint op2; uint result; uint lineno; uchar opcode; uchar op1_type; uchar op2_type; uchar result_type; } MyZendOp ; string MyZendOpRepr ( MyZendOp &obj ) { string s; SPrintf( s, "%#x ( %#x, %#x ) : %u", obj.opcode, obj.op1, obj.op2, obj.lineno ); return( s ); } // ////////////////////////////////////////////////////////////////////////// // // // struct _zval_struct { // zend_value value; // union { // uint32_t type_info; // } u1; // union { // uint32_t lineno; // } u2; // } // typedef struct _MyZval { uint64 value ; uint type_info; uint lineno; } MyZval ; string MyZvalRepr ( MyZval &obj ) { string s; // // 010 Editor显示64位整数时有自己的格式符,不完全同C语言 // SPrintf( s, "0x%Lx : %u : %u", obj.value, obj.type_info, obj.lineno ); return( s ); } // ////////////////////////////////////////////////////////////////////////// // // // struct _zend_op_array { // zend_uchar type; // uint32_t last; // zend_op *opcodes; // int last_var; // zend_string **vars; // zend_string *filename; // int last_literal; // zval *literals; // } // typedef struct _MyZendOpArray { local int saved_pos; // // local int i, j, k; // uchar type; uint last; // // An alternate way of specifying the format for a variable is to use // the syntax '' after a variable // declaration or a typedef. // uint opcodes_off ; // // Returns the current read position of the file. This read position // is used when defining variables in a Template. Every time a // variable is defined in a template, the read position moves ahead // the number of bytes used by the variable. // saved_pos = FTell(); // // Sets the current read position to the address pos. // // FSeek( opcodes_off ); // j = opcodes_off - 4; // for ( i = 0; i < last; i++ ) // { // // // // Returns data read from the file at address pos. If no pos is // // given, pos defaults to the current read position as reported by // // FTell. These functions can be used in a Template to read data // // from a file without declaring a variable and note that these // // functions do not affect the current read position. // // // k = ReadUInt( opcodes_off + i * 4 ); // FSeek( j + k ); // MyZendOp myzendop; // } // FSeek( opcodes_off + last * 4 ); MyZendOp opcodes[last]; FSeek( saved_pos ); uint last_var; // // The syntax '' can be used to hide the display of // variables in the Template Results. // uint vars_off ; saved_pos = FTell(); // // FSeek( vars_off ); // j = vars_off - 4; // for ( i = 0; i < last_var; i++ ) // { // k = ReadUInt( vars_off + i * 4 ); // FSeek( j + k ); // MyString mystring; // } // FSeek( vars_off + last_var * 4 ); MyString vars[last_var]; FSeek( saved_pos ); uint filename_off ; saved_pos = FTell(); FSeek( filename_off ); MyString filename ; FSeek( saved_pos ); uint last_literal; uint literals_off ; saved_pos = FTell(); // // FSeek( literals_off ); // j = literals_off - 4; // for ( i = 0; i < last_literal; i++ ) // { // k = ReadUInt( literals_off + i * 4 ); // FSeek( j + k ); // MyZval myzval; // } // FSeek( literals_off + last_literal * 4 ); MyZval literals[last_literal]; FSeek( saved_pos ); } MyZendOpArray; // ////////////////////////////////////////////////////////////////////////// // // // Indicates that all subsequent reads and writes from the file should use // little-endian byte order. // LittleEndian(); MyZendOpArray myzendoparray; -------------------------------------------------------------------------- 在010 Editor中打开SerializeTest.bin,Ctrl-F5打开SerializeTest.bt,F5运行之。 上述模板文件是自解释的,实际试试就知道怎么写、怎么读。从最后一行开始读,依 次套用结构、解析成员、显示数据、调用回调等等。 SerializeTest.bin中包含有some_off,但SerializeTest.bt用将之隐 藏了,转而在附近显示反序列化之后的some[],尽可能接近人类可读状态。 MyString是个复杂结构,不是单纯的char[],模板用回调函数 获取其中的char[],使得GUI中Value列的显示更直观。 010 Editor自带的帮助还可以,我都没怎么看在线帮助,直接看二进制自带的帮助。 1) 元素为变长结构的结构数组 typedef struct _MyString { uint id; uint len; char val[len]; } MyString; MyString是个变长结构。 MyString vars[last_var]; SerializeTest.bt中上面这种写法有问题,只不过SerializeTest.bin没有触发BUG。 SerializeTest_1.py这样构造vars[]: vars = \ [ MyString( 0, "Test_0" ), MyString( 1, "Test_1_1" ), MyString( 2, "Test_2_2_2" ), ] vars[0]、vars[1]、vars[2]均不等长,生成的SerializeTest_1.bin用 SerializeTest.bt解析时,Output窗口有警告: Optimizing array of structures may cause incorrect results. Use to override. 默认,此时010 Editor假设结构数组中所有元素大小同第一个元素; 对于变长结构形成的结构数组,这显然有问题。解决此类问题有两种办法,第一种办 法: typedef struct _MyString { uint id; uint len; char val[len]; } MyString ; 对于变长结构,建议始终在定义结构时使用。第二种办法: MyString vars[last_var] ; 在具体声明变长结构数组时指定。 显然第一种办法更理想。无论使用哪种办法,都有一个副作用,的 变长结构数组退化成Duplicate Array,而不是普通Array。 Duplicate Array与Array在GUI中的显示方式不同,后者的所有元素可以收缩成一行, 前者直接显示所有元素,无法收缩成一行。如果元素个数很多,无法收缩将非常不美 好。用SerializeTest_1.bt解析SerializeTest_1.bin能看到这种退化后的显示效果。 2) SerializeTest_1.bt 主要就一处改动 typedef struct _MyString { uint id; uint len; char val[len]; } MyString ; SerializeTest_1.py生成SerializeTest_1.bin,用SerializeTest_1.bt解析之。 $ xxd -g 1 SerializeTest_1.bin 00000000: 41 02 00 00 00 1d 00 00 00 03 00 00 00 4d 00 00 A............M.. 00000010: 00 89 00 00 00 02 00 00 00 9b 00 00 00 0c 00 00 ................ 00000020: 00 20 00 00 00 01 00 00 00 02 00 00 00 03 00 00 . .............. 00000030: 00 04 00 00 00 05 06 07 08 31 00 00 00 32 00 00 .........1...2.. 00000040: 00 33 00 00 00 34 00 00 00 35 36 37 38 10 00 00 .3...4...5678... 00000050: 00 1e 00 00 00 2e 00 00 00 00 00 00 00 06 00 00 ................ 00000060: 00 54 65 73 74 5f 30 01 00 00 00 08 00 00 00 54 .Test_0........T 00000070: 65 73 74 5f 31 5f 31 02 00 00 00 0a 00 00 00 54 est_1_1........T 00000080: 65 73 74 5f 32 5f 32 5f 32 03 00 00 00 0a 00 00 est_2_2_2....... 00000090: 00 54 65 73 74 5f 33 2e 70 68 70 0c 00 00 00 1c .Test_3.php..... 000000a0: 00 00 00 33 22 11 00 ff ff ff ff 06 00 00 00 0c ...3"........... 000000b0: 00 00 00 77 66 55 44 fe fe fe fe 07 00 00 00 0d ...wfUD......... 000000c0: 00 00 00 ... ☆ QA 1) 向Output窗口输出内容 Q: 010 Editor模板文件中用什么函数向Output窗口输出内容,类似printf()这种,有时 想用printf大法查看某处数据。 A: liushuisheng 用Printf()或MessageBox()。 2) Custom Variables Read Functions for Structs 3) Declaring Template Variables Hidden Variables 4) Arrays, Duplicates, and Optimizing Arrays and Duplicates Optimizing Arrays of Structs 5) 将字符串转换成整数 Templates and Scripts Writing Scripts Function Reference String Functions int Atoi( const char s[] ) 6) 从hexdump中反向定位模板字段 假设在hexdump中看到特征字节流,点中它,按Ctrl-J跳转到模板字段,或 Search->Jump To Template Variable (Ctrl-J) ☆ 进阶问题 1) 将一批整数映射到字符串 假设有 -------------------------------------------------------------------------- #define IS_CONST (1<<0) #define IS_TMP_VAR (1<<1) #define IS_VAR (1<<2) #define IS_UNUSED (1<<3) #define IS_CV (1<<4) -------------------------------------------------------------------------- 在binary中只有整数1、2、4、8、16,bt文件中如下代码将这些数字映射成字符串 -------------------------------------------------------------------------- local enum OpcodeTypeMap { IS_CONST = 1, IS_TMP_VAR = 2, IS_VAR = 4, IS_UNUSED = 8, IS_CV = 16 }; string GetOpcodeTypeStr ( OpcodeTypeMap opcode_type ) { return( EnumToString( opcode_type ) ); } -------------------------------------------------------------------------- GetOpcodeTypeStr(1)返回"IS_CONST",GetOpcodeTypeStr(16)返回"IS_CV"。 这是比较优雅的办法,第二种不优雅的方案是: -------------------------------------------------------------------------- local struct { string str; } OpcodeMap[2]; OpcodeMap[0].str="NOP"; OpcodeMap[1].str="ADD"; string GetOpcodeStr ( uchar opcode ) { return( OpcodeMap[opcode].str ); } -------------------------------------------------------------------------- GetOpcodeStr(0)返回"NOP"。如果数组元素特别多,第二种方案很难看,写起来倒没 什么,用awk处理一下C语言的#define即可。 第三种更蠢的方案是switch,不多说。 bt模板不支持多维数组,字符串数组本质上是多维数组,在帮助中Limitations有讲。 尝试过下面这几种失败的写法,报语法错。 -------------------------------------------------------------------------- local struct { string str; } OpcodeMap[2] = { "NOP", "ADD" } -------------------------------------------------------------------------- local struct { string str; } OpcodeMap[2] = { {"NOP"}, {"ADD"} } -------------------------------------------------------------------------- 2) 显示格式化过的时间字符串 C代码的time_t随32/64位自动变化,bt中time_t是32位的,time64_t才是64位的,若 从C代码移植结构定义,务必注意这点。 time64_t的格式串固定为"MM/dd/yyyy hh:mm:ss",若想换其他格式串,并使之在GUI 中Value列生效,有一种方案 -------------------------------------------------------------------------- typedef struct _time_t_struct { uint64 t ; } time_t_struct ; string time_t_struct_callback ( time_t_struct &obj ) { return( t2str( obj.t ) ); } string t2str ( uint64 t ) { return( TimeTToString( t, "yyyy/MM/dd hh:mm:ss" ) ); } -------------------------------------------------------------------------- 然后在模板中不用time64_t,转用自定义结构time_t_struct。当然,此处说的是修 改GUI中Value列的显示,若只想修改Comment列的显示,无需这种技巧。 若对GUI中Value列的显示有某种执念,上述技巧是种通用思路,不局限于时间变量, 适用于任意数据类型,要点就是用自定义结构再封装,动用。 3) 以64位为例,对比如下结构定义,第二种定义尾部使用了属性 -------------------------------------------------------------------------- typedef struct _zend_file_cache_metainfo { char magic[8]; char system_id[32]; size_t mem_size ; size_t str_size ; size_t script_offset ; time64_t timestamp; uint checksum ; } zend_file_cache_metainfo; -------------------------------------------------------------------------- typedef struct _zend_file_cache_metainfo { char magic[8]; char system_id[32]; size_t mem_size ; size_t str_size ; size_t script_offset ; time64_t timestamp; uint checksum ; } zend_file_cache_metainfo ; -------------------------------------------------------------------------- 第一种定义使得结构只有0x4c字节。若binary中该结构对齐在64位边界上,第一种结 构定义就会惹麻烦;修正方案是在checksum成员后面显式定义"uint pad",或者用 FSkip()占位,又或者采用第二种结构定义,不显式占位。 4) 结构优化对齐 前一小节是结构优化对齐问题中的特例。 bt模板中结构定义未启用结构优化对齐,不知有无官方启用方案? 若binary中相应结构是优化对齐过的,从C代码向bt模板移植结构定义,应该显式定 义各处的填充变量,比如 -------------------------------------------------------------------------- typedef struct _zend_persistent_script { zend_script script ; uint64 compiler_halt_offset ; uint ping_auto_globals_mask; // // 结构优化对齐带来的填充 // uint pad_0; time64_t timestamp; uchar corrupted; uchar is_phar; // // 结构优化对齐带来的填充 // uint16 pad_1; uint32 pad_2; ... } zend_persistent_script; -------------------------------------------------------------------------- 在结构优化对齐场景FSkip()占位没有特别优势,但如果是快速定义未知结构的场景, FSkip()占位是个不错的选择。 结构优化对齐给bt模板编写带来不小麻烦,从C代码向bt模板移植结构定义之前应仔 细检查各成员偏移,判断是否存在隐式变量对齐。参[3],推荐用FatalError提供的 py脚本进行此检查。 5) 变长结构中的变长成员变量 -------------------------------------------------------------------------- typedef struct _zend_string { zend_refcounted_h gc; uint64 h ; size_t len; if ( 0 != len ) { char val[len]; } } zend_string ; -------------------------------------------------------------------------- 上述结构中val[]的长度由len成员控制。若len为0,不用if而直接"char val[len]" 会引发一个告警,但不消除该告警也没事。问题不在于这种告警,而是后续引用val 之前务必判断len是否为0。对于bt模板来说,如下结构定义并不确保val成员变量存 在! -------------------------------------------------------------------------- typedef struct _zend_string { zend_refcounted_h gc; uint64 h ; size_t len; char val[len]; } zend_string ; -------------------------------------------------------------------------- 当len为0时,没有val变量,若强行引用,不是告警而是报错。这点与C语言的直觉不 同。 所有变长结构都不能求sizeof(),不管实际上变不变长,会报错。 6) 元素为变长结构的结构数组 -------------------------------------------------------------------------- typedef struct _MyString { uint id; uint len; char val[len]; } MyString; MyString vars[last_var]; -------------------------------------------------------------------------- MyString是变长结构,vars[]是元素为变长结构的结构数组,模板中上述写法有问题! 假设Python代码这样构造vars[]: vars = \ [ MyString( 0, "Test_0" ), MyString( 1, "Test_1_1" ), MyString( 2, "Test_2_2_2" ), ] vars[0]、vars[1]、vars[2]均不等长,生成的序列化数据用前述模板解析时,只有 vars[0]被正确解析,vars[1]、vars[2]均解析错误,与此同时Output窗口有警告: Optimizing array of structures may cause incorrect results. Use to override. 模板结构有默认属性,此时010 Editor假设结构数组中所有元素大 小同第一个元素,若结构元素定长,这是自然而然的事儿;但对于变长结构形成的结 构数组,这种假设显然有问题,上例中vars[1]被截断成vars[0]的大小,vars[2]就 更错位了。 解决此类问题有两种办法,第一种办法: -------------------------------------------------------------------------- typedef struct _MyString { uint id; uint len; if ( 0 != len ) { char val[len]; } } MyString ; -------------------------------------------------------------------------- 对于变长结构,建议始终在定义结构时使用。第二种办法: -------------------------------------------------------------------------- MyString vars[last_var] ; -------------------------------------------------------------------------- 在具体声明变长结构数组时指定。 第一种办法更理想。无论使用哪种办法,都有一个副作用,的变长 结构数组退化成"Duplicate Array",而不是普通Array。 Duplicate Array与Array在GUI中的显示方式不同,后者的所有元素可以收缩成一行, 前者直接显示所有元素,无法收缩成一行。如果元素个数很多,无法收缩将非常不美 好。 7) 变量的作用域 bt模板同C代码一样有作用域的概念,大致就是外层定义的模板变量、local变量可为 内层所用,内层可以定义同名local变量进行覆盖。如果需要全局local变量,就在最 外层定义,不需要特别技巧。 8) 字符串拼接 可以用+号进行字符串拼接,也可以用SPrintf()函数。 9) parentof()/startof() -------------------------------------------------------------------------- typedef struct _zend_op_array { ... zend_op opcodes[last]; ... } zend_op_array; typedef struct _zend_op { ... znode_op op1 ; ... uchar opcode ; ... } zend_op ; string op1_comment ( znode_op &op1 ) { uint64 opcodes_base, current_off; uint64 i; uchar opcode; opcodes_base = startof( parentof( op1 ) ); current_off = startof( op1 ); i = ( current_off - opcodes_base ) / sizeof( zend_op ); opcode = parentof( parentof( op1 ) ).opcodes[i].opcode; ... } -------------------------------------------------------------------------- 注意看回调函数op1_comment()的实现,有几点需要特别强调。 parentof(op1)不是opcodes[i],而是opcodes[],startof(parentof(op1))取到的是 opcodes[]的起始地址,而不是opcodes[i]的地址,这点与直觉相悖。换个更明显的 角度看这个坑,parentof(opcodes[i].op1)不对应opcodes[i]。若代码中出现 parentof(op1).op1_type,将固定返回opcodes[0].op1_type,而你本来想要的是 opcodes[i].op1_type。op1_comment()中已正确处理该问题。 此外,不能直接写parentof(op1)[i].opcode,报语法错,必须写成 parentof(parentof(op1)).opcodes[i].opcode。 zend_op结构中并无指针指向zend_op_array结构,若是C编程,从地址空间布局反推 即可,若是其他语言编程,想从zend_op找回zend_op_array就不大方便。而 parentof()/startof()的存在为bt模板编写带来很大便利。 ☆ Construct vs 010 Editor 参[4]、[5] 从Ian Bouchard的php7-opcache-override注意到Python的Construct库,其使用上与 010 Editor模板有些像,都是定义结构、套用结构、解析成员,做二进制数据解析时 不错。不过这个库充满了神秘主义哲学,文档也很差。我把它官方文档过了一遍,感 觉作者自嗨得不行。 入门的话,从Ian Bouchard的opcache_parser_64.py看起就可以。这个用的是2.8版 Construct库,现在是2.10版。最好用Python3、Construct 2.10写自己的解析代码, 以后好维护。 有些人写Python代码,神经质地定义各种类,等你仔细一看,发现这些类完全体现不 出存在的意义,到处调用外部自定义函数、引用全局变量,合着就是为定义类而定义 类,非类不写代码,这是病,得治。 010 Editor容错性强于Construct,当某个结构有问题时,前者仍可显示前面正确部 分,后者要求全部正确。 ☆ 参考资源 [1] 010 Editor Online Manual https://www.sweetscape.com/010editor/manual/ Limitations https://www.sweetscape.com/010editor/manual/TemplateLimitations.htm String Functions https://www.sweetscape.com/010editor/manual/FuncString.htm Interface Functions https://www.sweetscape.com/010editor/manual/FuncInterface.htm https://www.sweetscape.com/010editor/manual/FuncInterface.htm#SetBackColor (动态设置颜色) Using the Debugger https://www.sweetscape.com/010editor/manual/Debug.htm How does scope work when defining local variables https://www.sweetscape.com/support/kb/kb1025.html [2] 010Editor脚本语法入门 https://www.jianshu.com/p/ba60ebd8f916 [3] How to get the relative address of a field in a structure dump - [2012-03-20] https://stackoverflow.com/questions/9788679/how-to-get-the-relative-address-of-a-field-in-a-structure-dump-c 《有调试符号的情况下在GDB中获取结构成员的偏移》 https://scz.617.cn/unix/201203201430.txt [4] Construct https://construct.readthedocs.io/en/latest/ https://construct.readthedocs.io/en/latest/genindex.html https://github.com/construct/construct/ https://github.com/construct/construct/releases [5] Binary Webshell Through OPcache in PHP 7 - Ian Bouchard [2016-04-27] https://www.gosecure.net/blog/2016/04/27/binary-webshell-through-opcache-in-php-7/ Detecting Hidden Backdoors in PHP OPcache - Ian Bouchard [2016-05-26] https://www.gosecure.net/blog/2016/05/26/detecting-hidden-backdoors-in-php-opcache/ PHP OPcache Override https://github.com/GoSecure/php7-opcache-override https://github.com/GoSecure/php7-opcache-override/issues/6