使用 protoc 重新编码 protobuf,没有架构 带有要编码的 RecordList 数据的示例文本文件我测试过的原型文件版本让我们做一个编码的例子,我假设一个Linux环境

问题描述

我正在使用一个应用程序并尝试对其可以导出和导入的数据文件进行逆向工程。这些文件是二进制的 protobufs。我的目标是能够导出文件、转换为文本、使用附加数据记录对其进行修改、重新编码为二进制文件并重新导入它,以绕过繁琐的手动数据输入应用程序。我在带有 protoc 的 Windows 机器上使用了 --decode_raw 二进制文件,并且可以在不知道实际使用的 .proto 架构的情况下生成可读性很好的分层数据。使用 marc Gravell 的解析器给出了类似的结果(有一些我不太明白的歧义。)我的问题如下:

  1. 是否有一种简单的方法可以使用 --decode_raw 或其他工具重新编码 protoc输出生成原始二进制文件?我知道原始解码正在对未知模式做出假设,到目前为止,这些假设看起来可以产生可理解的结果。原始解码中是否会丢失数据以防止重新编码为原始数据?仅仅是 protoc 开发人员认为不需要此功能吗?借助此功能,我可以修改文本并重新编码,并且很有可能生成有效的二进制文件
  2. 如果 #1 不可行,鉴于原始输出,我如何创建 .proto 文件和文本消息输入文件以使用 protoc --encode 重新编码原始二进制文件?我希望能有一个指向示例文本文件的指针,这些文件可以用作 protoc 的命令行输入,以便我学习所需的语法。我所看到的示例内容似乎都是针对使用 protoc 生成代码的。我测试的二进制 protobufs 已经解码为字符串、整数和一些十六进制值(我仍然需要解密),它们与应用程序中可见的数据很好地对应,所以我有信心如果我看到工作,我可以制作所需的模式示例。

一些偏好:我正在修修补补我的手机和我的 Windows 笔记本电脑,宁愿不需要安装 python 或其他编程平台。我只想在命令行上使用 protoc,以及我的文本/十六进制编辑器。

感谢您的帮助。

[编辑:我找到了一个提供示例输入的网页,它为我提供了取得进展所需的线索。该页面https://medium.com/@at_ishikawa/cli-to-generate-protocol-buffers-c2cfdf633dce,所以感谢 @at_ishikawa 花时间制作它。通过示例,我了解了如何格式化消息文件生成二进制文件。但是,看起来我尝试解码的二进制文件可能不适合命令行。请参阅下面我的新问题。]

新问题:我的目标仍然是将二进制文件解码为文本消息,编辑文本消息以添加更多数据记录,并重新编码修改后的文本消息以制作一个新的二进制文件,希望能够成功导入应用程序。 使用--decode_raw,我可以看到我的二进制文件具有以下格式:


    1 {
      1: "ThisItem:name1"
      2 {

        1: "name1"
        2: <string>
        4: <string>
        5: 1
      }
    }
    
    1 {
      1: "ThisItem:name2"
      2 {
        1: "name2"
        2: <string>
        4: <string>
        5: 1
      }
    }
    
    1 {
      1: "ThatItem:name1"
      2 {
        1: "name1"
        3: <string>
        5: <data structure>
        8: <string>
      }
    }
    
    1 {
      1: "ThatItem:name2"
      2 {
        1: "name2"
        3: <string>
        5: <data structure>
        8: <string>
      }
    }
    
    1 {
      1: "ThisItem:name3"
      2 {
        1: "name3"
        2: <string>
        4: <string>
        5: 1
      }
    }

所以我看到了数据结构的几个特点:

  1. 文件是多条记录的串联,每条记录都具有相同的字段编号 1。因此它们需要使用相同的消息格式。
  2. 每条记录在字段编号 2 中都有一个子消息,但这些消息有两种不同的格式。
  3. 每条记录都有一个字段编号 1,它是一个前缀“ThisItem”或“ThatItem”,它显然标识了字段 2 的消息类型,以及与“内部”消息中的第一个字符串匹配的后缀。

然后我可以制作一个 .proto 文件来几乎支持这种结构:


    Syntax = "proto3";
    
    message RecordList {
        repeated Record records = 1;
    }
    
    message Record {
        string id = 1;
        ThisItem item = 2;
        ThatItem item = 2;  //  Problem here,each record uses field 2,but with different message types.
    //  Each record has either a ThisItem or ThatItem.  Parsing the id field Could tell which,//  but that doesn't appear possible with protoc on the command line.
    }
    
    message ThisItem {
        string id = 1;
        string <element2> = 2;
        string <element4> = 4;
        int32 <element5> = 5;
    }
    
    message ThatItem {
        string id = 1;
        string <element3> = 3;
        <message type> <element5> = 5;
        string <element8> = 8;
    }

所以我不确定是否有办法在命令行上解码/编码这个二进制文件。通过解析字段 1 中的字符串,是否有一些语法可以用于 Record 消息以在字段 2 的两种可能选择之间切换?如果没有,我将需要在程序中读取和解析记录,这是我想要避免的。

我意识到的另一种可能性:代替两个不同的子消息 ThisItemThatItem,我可以使用一个子消息并跳过未使用的字段。在一种情况下,子消息将填充字段 1、2、4 和 5,在另一种情况下填充字段 1、3、5 和 8。难点是字段 5,一种情况是整数 1,另一种情况是数据结构。我不知道如何管理。整数 1 是空消息的二进制编码吗?

感谢您的帮助。

解决方法

让我看看能不能帮忙回答这个问题。

1.使用 protoc 重新编码

protoc --decode_raw 是一条死胡同,以后不能用protoc 编码。这是因为没有 --encode_raw 这样的东西。你不能有一个带有名为 1,2,3 等消息的 proto 文件......它不起作用。但是,如果可以设置模式来匹配数据,您就可以轻松地提供 protoc 进行编码或解码。

带有要编码的 RecordList 数据的示例文本文件

这是用于对消息进行编码的文本,对于下面的示例,我已将其保存到名为 message 的文件中。

records{
      id: '1'
      item{
      id: '1.1'
      element2: 'e2'
      element4: 'e4'
      element5: 5
      }
}
records{
      id: '2'
      item{
      id: '2.1'
      element2: 'e2'
      element4: 'e4'
      element5: 5
      }
}

我测试过的原型文件版本

我在下面的示例中将此文件命名为 test.proto。

syntax = "proto3";

message RecordList {
    repeated Record records = 1;
}

message Record {
    string id = 1;
    ThisItem item = 2;
}

message ThisItem {
    string id = 1;
    string element2 = 2;
    string element4 = 4;
    int32 element5 = 5;
}

让我们做一个编码的例子,我假设一个Linux环境

# Encode message and then decode it with our schema
protoc --encode="RecordList" --proto_path= ./test.proto < message | protoc --decode="RecordList" --proto_path= ./test.proto

输出:

records {
  id: "1"
  item {
    id: "1.1"
    element2: "e2"
    element4: "e4"
    element5: 5
  }
}
records {
  id: "2"
  item {
    id: "2.1"
    element2: "e2"
    element4: "e4"
    element5: 5
  }
}

此时您可以修改此输出以具有您想要的不同值并对其进行编码。使用像 hd 或 xxd 这样的十六进制转储工具也非常有用!

# Send output from protoc decoding to be encoded again with different 
# Lets say you saved the output to out.txt 
protoc --encode="RecordList" --proto_path= ./test.proto < out.txt

# You can always decode your output to see the formatted parsed version
protoc --encode="RecordList" --proto_path= ./test.proto < out.txt | protoc --decode="RecordList" --proto_path= ./test.proto 

问题 2 创建模式并使用 protoc --encode

我想查看您的 --decode_raw 命令的原始输出,即使将有效负载视为像 08-24-AF-22 这样的十六进制字符串也会有所帮助。 如果没有看到,我真的无法就消息类型的差异提出任何建议。

例如,我知道 int32 类型的负数将显示为带有 --decode_raw 的无符号大整数。如果没有更仔细地查看原始数据,我不确定您的案例会发生什么。

但是是的,我建议将消息​​ ThisThing 和 ThatThing 结合起来。不包括每个消息中的每个消息字段是很常见的。

如果您还没有找到这个online decoder by Marc Gravell 那么一定要尝试一下。它本质上是protoc --decode_raw,有更多细节。

,

由于看起来命令行上的 protoc 不能做我想做的事,我转向编写程序。对我来说最简单的方法是安装 python,因为学习曲线看起来不太陡峭,我可以一点一点地构建一个脚本。数据结构的关键是替换我假设的 .proto 文件的这一部分:

message Record {
    string id = 1;
    ThisItem item = 2;
    ThatItem item = 2;  //  Problem here,each record uses field 2,but with different message types.
//  Each record has either a ThisItem or ThatItem.  Parsing the id field could tell which,//  but that doesn't appear possible with protoc on the command line.
}

具有广义形式:

message Record {
    optional string id = 1;
    oneof datafields {
        bytes data = 2;
        ThisItem thisitem = 3;
        ThatItem thatitem= 4;
    }
}

protobuf 二进制文件只使用通用的字节数据结构,这就是为什么 protoc--decode_raw 使用字段编号 2 显示所有数据。data 字段可以是一个根据需要为 ThisItemThatItem 提供容器。这两个结构也尽可能包含在 datafields 中,以便程序记录结构可以容纳它们以进行编程操作。

这是 python 的示例代码,其中 .proto 文件是 myschema.proto,定义如我上面的问题所示:

import myschema_pb2
from google.protobuf import text_format

### Read objects from PB and load into RecordList
mylist=myschema_pb2.RecordList()
f=open('objects.pb','rb')
mylist.ParseFromString(f.read())
f.close()
    
### Parse general data into ThisItem or ThatItem
for rec in mylist.records:
  bin1 = rec.data
  ss=rec.id
  itemID=ss[0:ss.find(':')]
  if itemID == 'ThisItem':
    rec.thisitem.ParseFromString(rec.data) # parses data into thisitem and clears data
  elif itemID == 'ThatItem':
    rec.thatitem.ParseFromString(rec.data) # parses data into thatitem and clears data
  else:
    print('unknown')

Thisitem 和 thatitem 然后可以根据需要进行操作。当需要编写 protobuf 文件时,它们会被转换回通用数据格式:

### Generalize ThisItem and ThatItem into data
for rec in newlist.records:
  ss=rec.id
  itemID=ss[0:ss.find(':')]
  if itemID == 'ThisItem':
    rec.data=rec.thisitem.SerializeToString()
  elif itemID == 'ThatItem':
    rec.data=rec.thatitem.SerializeToString()
  else:
    print('unknown')

再次注意,这种结构是我一直在使用的 protobuffer 所特有的。我不确定为什么开发人员决定这样做,而不是将 thisitem 和 thatitem 写入二进制文件。据我所知,它改变的只是字段编号,2、3 或 4。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...