プロトコルバッファリバースエンジニアリング

このコンテキストでのリバースエンジニアリングとは、開発者が使用した元のメッセージスキームに最も近い元のメッセージスキームを復元することを意味します。 必要なものを取得する方法はいくつかあります。 まず、クライアントアプリケーションにアクセスできる場合、開発者はデバッグシンボルを非表示にし、protobufライブラリのLITEバージョンにリンクすることに注意しなかったため、元の.protoファイルを簡単に取得できます。 第二に、開発者がLITEライブラリアセンブリを使用する場合、これはもちろんリバーサーの寿命を複雑にしますが、逆に役に立たないわけではありません:特定のコツで、この場合でも、元のファイルに非常に近い.protoファイルを復元できます。



この記事では、ptobobufメッセージを逆にするためのいくつかのテクニックについて説明したいと思います。おかげで、私のprotodecプロジェクトが登場しました。 述べたことはすべて、バージョン2のprotobufメッセージのエンコード形式に関連していることに注意してください(バージョン3はまだサポートされていません、パックされたフィールドも)。



準備する



まず、研究用のオブジェクトを作成します。 2つのファイルが必要です。



addressbook.proto
package tutorial; option optimize_for = LITE_RUNTIME; message Person { required string name = 1; required int32 id = 2; optional string email = 3; enum PhoneType { MOBILE = 0; HOME = 1; WORK = 2; } message PhoneNumber { required string number = 1; optional PhoneType type = 2 [default = HOME]; } repeated PhoneNumber phone = 4; } message AddressBook { repeated Person person = 1; }
      
      





tut.cpp
 #include <iostream> #include <cassert> #include <string> #include "addressbook.pb.h" int main() { GOOGLE_PROTOBUF_VERIFY_VERSION; tutorial::AddressBook book; tutorial::Person * person = book.add_person(); person->set_id(1234); person->set_name("John Doe"); person->set_email("jdoe@example.com"); tutorial::Person_PhoneNumber * phone = person->add_phone(); phone->set_number("555-4321"); phone->set_type(tutorial::Person_PhoneType_HOME); std::string data = book.SerializeAsString(); assert(!data.empty()); std::cout.write(&data[0], data.size()); google::protobuf::ShutdownProtobufLibrary(); }
      
      





それらを保存し、すべてまとめます。 protocが何かわからない場合は、プログラミング言語のProtobufライブラリの概要を読む必要があります。



 protoc --cpp_out=. addressbook.proto && g++ addressbook.pb.cc tut.cpp `pkg-config --cflags --libs protobuf` -s -o tut.lite.exe && ./tut.lite.exe > A
      
      





addressbook.protoファイルの2行目を削除またはコメントアウトして、コマンドを実行します。



 protoc --cpp_out=. addressbook.proto && g++ addressbook.pb.cc tut.cpp `pkg-config --cflags --libs protobuf` -o tut.exe && ./tut.exe > B
      
      





上記のコマンドを実行すると、2つの実行可能ファイルtut.lite.exeとtut.exeがあり、それぞれLITEとlibprotobufライブラリの完全なアセンブリが含まれています。 どちらのプログラムも同じことを行います。protobufメッセージが作成され、std :: coutに表示されます。 また、AとBという名前の2つのバイナリファイルも取得しました。最初のファイルはライトバージョンによって生成され、2番目はプログラムのフルバージョンによって生成されます。 それらの内容は同じです。 以下のスクリーンショットでは、このメッセージのバイナリ表現とそのテキストビューを見ることができます。







addressbook.protoを削除して、復元を試みます。



記述子実行可能ファイルデータからのメッセージスキーマの回復



protocユーティリティによって以前に生成されたadressbook.pb.ccファイルの内容を見てください。 protobuf_AddDesc_addressbook_2eproto関数に興味があるはずです。 最初のアクションの1つは、関数:: google :: protobuf :: DescriptorPool :: InternalAddGeneratedFileを呼び出すことです。その最初の引数は、元のメッセージの構造に関する情報を含むDescriptor protobufメッセージです。



 // ... void protobuf_AddDesc_addressbook_2eproto() { static bool already_here = false; if (already_here) return; already_here = true; GOOGLE_PROTOBUF_VERIFY_VERSION; ::google::protobuf::DescriptorPool::InternalAddGeneratedFile( "\n\021addressbook.proto\022\010tutorial\"\332\001\n\006Person" "\022\014\n\004name\030\001 \002(\t\022\n\n\002id\030\002 \002(\005\022\r\n\005email\030\003 \001(" "\t\022+\n\005phone\030\004 \003(\0132\034.tutorial.Person.Phone" "Number\032M\n\013PhoneNumber\022\016\n\006number\030\001 \002(\t\022.\n" "\004type\030\002 \001(\0162\032.tutorial.Person.PhoneType:" "\004HOME\"+\n\tPhoneType\022\n\n\006MOBILE\020\000\022\010\n\004HOME\020\001" "\022\010\n\004WORK\020\002\"/\n\013AddressBook\022 \n\006person\030\001 \003(" "\0132\020.tutorial.Person", 299); ::google::protobuf::MessageFactory::InternalRegisterGeneratedFile( "addressbook.proto", &protobuf_RegisterTypes); Person::default_instance_ = new Person(); Person_PhoneNumber::default_instance_ = new Person_PhoneNumber(); AddressBook::default_instance_ = new AddressBook(); Person::default_instance_->InitAsDefaultInstance(); Person_PhoneNumber::default_instance_->InitAsDefaultInstance(); AddressBook::default_instance_->InitAsDefaultInstance(); ::google::protobuf::internal::OnShutdown(&protobuf_ShutdownFile_addressbook_2eproto); } // ...
      
      





列挙、インポートリスト、メッセージ、名前、フィールドのデータ型などに関する情報を保存します。 この形式は秘密ではなく、ソースコードが付属しています。 google / protobuf / descriptor.protoで見ることができます。 このデータは、リフレクション、メッセージコンテンツの出力のデバッグなどに使用されます。



protodecユーティリティは、バイナリファイルで記述子データを検索し、それらから回復された.protoファイルを保存できます。 これを行うには、次のコマンドを実行します。



 protodec --grab tut.exe
      
      





応答として、次のようなものが表示されます。







つまり、最終的にはほぼ元のソース.proto-fileが得られました。



メッセージバイトからスキームを回復する



アプリケーションへのアクセスがない場合(たとえば、サーバー上のどこかで機能する場合)、データをDescriptorに取得することも困難になります。 アプリケーションがLITE最適化で構築されている場合も同じことが当てはまります。リフレクションは使用されないため、.protoファイルの記述子記述はコンパイル段階で生成されないため、上記の方法を使用して元の.protoファイルを復元することはできません。 この場合、protobufメッセージの内容の分析を試みることができます。 それらは100%同じ構造でなければならないことに注意してください(ルートメッセージは同じでなければなりません)。 このようなメッセージは可能な限り必要です。 データが多いほど、結果はより良くなります。



protodecプログラムは、ファイルからタイプをロードして、指定されたprotobufメッセージのスキームを復元できます。 これを行うには、次のコマンドを実行します。



 protodec --schema A
      
      









この結論は、このprotobufメッセージ(ファイルAからダウンロード)で3つのメッセージが検出されたことを意味します。 元のaddressbook.protoを見ると、MSG1はPerson :: PhoneNumber、MSG2はPerson、MSG3はAddressBookです。 顕著な差異について説明します。



  1. フィールドMSG3.fld1を繰り返す必要があります。 ここでの問題は、元のメッセージのAddressBook.personには要素が1つしかなく、バイナリレベルではこの場合の繰り返しフィールドを区別できないことです。 AddressBook.personに少なくとも2つのデータ要素がある場合、それは正しく決定されます。 そのため、最大の占有率でこのスキームのいくつかのメッセージが必要です。



  2. 一部の必須フィールドはオプションである必要があります。 この問題は、多数のメッセージを分析することによっても解決されます。これにより、必要なフィールドの場所とオプションの場所を理解できます。



  3. MSG2.fld2フィールドはint32でなければならず、int64です。 低レベルでは、protobufでは、すべての整数型(int32、int64、uint32、uint64、sint32、sint64、bool、enum)がVarintとして保存されます。 次に、このフィールドの数値が符号付きか符号なしかをコンテキストから理解できます。int64は、使用されるプログラミング言語の最大可能整数値を格納できるように選択されます。


フィールドとメッセージの両方の名前が自動的に生成されます。これらのメタデータをprotobufメッセージ自体から取得することは不可能です。 それらは単にそこにありません。 この場合、調査中のメッセージのコンテキストからメッセージとフィールドの目的が多少明らかになったら、メッセージとフィールドの名前を徐々に変更できます。 また、アプリケーション自体のエクスポートリストで、この情報を見つけることができます。 そのためには、IDAなど、これを実行できるユーティリティが必要です。 ここでは、チュートリアルの名前とフィールドの順序を抽出しました::: 4つのフィールドを持つPersonメッセージ:









残りのメッセージについても同じことを行い、その結果、ほとんど元の.protoファイルが取得されます。



確認する



その結果、次のような.protoファイルが得られました。



tut2.proto
 package ProtodecMessages; message PHONE { required string Number = 1; required int64 Type = 2; } message PERSON { required string Name = 1; required int64 Id = 2; required string Email = 3; required PHONE Phone = 4; } message ADDRESSBOOK { repeated PERSON Person = 1; }
      
      





復元した回路が元のメッセージを編集できることを確認する小さなプログラムを作成します。



tut2.cpp
 #include <iostream> #include <fstream> #include <string> #include <cassert> #include "tut2.pb.h" int main() { GOOGLE_PROTOBUF_VERIFY_VERSION; //   protobuf   std::cin std::string data; ProtodecMessages::ADDRESSBOOK book; while (std::cin.peek() != EOF) data.push_back((char)std::cin.get()); //    ? assert(book.ParseFromString(data)); assert(book.person_size() > 0); //   ProtodecMessages::PERSON * person = book.mutable_person(0); person->set_email("fake@name.com"); person->set_id(4321); //     std::cout data = book.SerializeAsString(); assert(!data.empty()); std::cout.write(&data[0], data.size()); // Optional: Delete all global objects allocated by libprotobuf. google::protobuf::ShutdownProtobufLibrary(); }
      
      





コンパイルして実行します:



 protoc --cpp_out=. tut2.proto && g++ tut2.pb.cc tut2.cpp `pkg-config --cflags --libs protobuf` -o tut2.exe
      
      











参照:






All Articles