Protobuf 101

Что в программе

  • Зачем нужен?
  • Как отправить запрос?
  • Что делать, когда нет proto файла
  • gRPC

Зачем нужен?

  • Быстрее
  • Меньше
  • "Swagger included"

proto file


syntax = "proto2";

option optimize_for = LITE_RUNTIME;

package autofill;

import "password_requirements.proto";

message AutofillUploadContents {
  required string client_version = 1;
  required fixed64 form_signature = 2;

  // The secondary form signature is calculated based on field types instead of
  // names and is used if the primary one is unstable, i.e. the field names
  // change on every page load. Not used currently.
  optional fixed64 secondary_form_signature = 34;

  // True if the autofill feature was used to fill this form, false otherwise.
  required bool autofill_used = 3;

  // A string representing a bit array of what personal information items
  // the user has entered in the autofill settings dialog.
  // The corresponding bit is set if the user has that particular
  // item entered and is not set otherwise.
  required string data_present = 4;

  // List of the fields in the form and their types.
  repeated group Field = 5 {
    // Field identification inside the current form.
    required fixed32 signature = 6;

    // Type of the field, e.g. what type of personal information did the user
    // enter in that field before form submission. There is a predefined
    // enum of types located at
    // components/autofill/core/browser/field_types.h
    // AutoFillFieldType
    repeated fixed32 autofill_type = 7;

    // The value of the name attribute on the field, if present. Otherwise, the
    // value of the id attribute. See HTMLFormControlElement::nameForAutofill.
    // TODO(850606): Deprecate once randomized metadata is launched.
    optional string name = 8;

    // The value of the autocomplete attribute on the field, if present.
    // TODO(850606): Deprecate once randomized metadata is launched.
    optional string autocomplete = 9;

    // The type of input control for this field (e.g. text, textarea, email).
    // TODO(850606): Deprecate once randomized metadata is launched.
    optional string type = 10;

    // The field-level metadata associated with this field, randomized.
    optional AutofillRandomizedFieldMetadata randomized_field_metadata = 33;

    enum PasswordGenerationType {
      NO_GENERATION = 0;
      AUTOMATICALLY_TRIGGERED_GENERATION_ON_SIGN_UP_FORM = 1;
      AUTOMATICALLY_TRIGGERED_GENERATION_ON_CHANGE_PASSWORD_FORM = 2;
      MANUALLY_TRIGGERED_GENERATION_ON_SIGN_UP_FORM = 3;
      MANUALLY_TRIGGERED_GENERATION_ON_CHANGE_PASSWORD_FORM = 4;
      IGNORED_GENERATION_POPUP = 5;
    }
    // The type of password generation, if it happened.
    optional PasswordGenerationType generation_type = 17;

    // The value of the class attribute on the field, if present.
    // TODO(850606): Deprecate once randomized metadata is launched.
    optional string css_classes = 19;

    // The properties mask (i.e. whether the field was autofilled, user
    // modified, etc.) See FieldPropertiesFlags.
    optional uint32 properties_mask = 20;

    // The value of the id attribute, if it differs from the name attribute.
    // Otherwise, this field is absent.
    // TODO(850606): Deprecate once randomized metadata is launched.
    optional string id = 21;

    // True iff the user changed generated password. If there was no generation,
    // the field is absent.
    optional bool generated_password_changed = 22;

    enum VoteType {
      NO_INFORMATION = 0;
      // A credential saved on one form (typically a signup form) was used on a
      // login form. The vote applies to the first (signup) form.
      CREDENTIALS_REUSED = 1;
      // When reusing a credential, the username value is not the saved
      // username, but another value, which appeared on the form where we saved.
      // The correct field is voted for.
      USERNAME_OVERWRITTEN = 2;
      // In the save prompt, the user corrected the username value to another
      // value from the form. The new field is voted for.
      USERNAME_EDITED = 3;
      // The username field was detected by the base heuristic (take the last
      // non-password field before the first password field). The value is not
      // used at this point.
      BASE_HEURISTIC = 4;
      // The username field was detected by HTML-based detector. The value is
      // not used at this point.
      HTML_CLASSIFIER = 5;
      // A saved credential was used for the first time on a submitted form. The
      // vote applies to the form being submitted.
      FIRST_USE = 6;
    }

    // The type of password-related vote. If |autofill_type| is not a USERNAME
    // or any PASSWORD vote, then the field is absent. This field describes the
    // context of the vote.
    optional VoteType vote_type = 23;

    message AutofillTypeValiditiesPair {
      // Type of the field, e.g. what type of personal data the user entered in
      // that field before form submission. A list of all data types can be
      // found in: components/autofill/core/browser/field_types.h
      required int32 type = 1;
      // The validity of the type, which is determined based on the validity of
      // the user's profile data. A list of all validity states can be found
      // here: components/autofill/core/browser/data_model/autofill_data_model.h
      // The validity state of a type is used to experiment if only using valid
      // data would result in better predictions.
      // TODO(crbug.com/1174203): Remove the validity.
      repeated int32 validity = 2;
    }
    // A list of possible types for the field with their corresponding validity
    // states based on the user's data.
    repeated AutofillTypeValiditiesPair autofill_type_validities = 35;

    // A low-entropy hash of the field's initial value before user-interactions
    // or automatic fillings. This field is used to detect static
    // placeholders.
    optional uint32 initial_value_hash = 40;
  }
  // Signature of the form action host (e.g. Hash64Bit("example.com")).
  optional fixed64 action_signature = 13;

  // Signature of the form. This is currently used when password generated on a
  // password field of a registration form is used on a password field of a
  // login form.
  optional fixed64 login_form_signature = 14;

  // Whether a form submission event was observed.
  optional bool submission = 15;

  // The form name.
  optional string form_name = 16;

  // True if the non-obfuscated password values were shown to the user.
  optional bool passwords_revealed = 24;

  // The section of noisified data about password.
  // Upload only one of character class attributes (|password_has_*|). Noisified
  // length is always uploaded.
  // Upload only when a password is saved.
  // Used to adjust the password generator's settings to site's requirements.

  // Whether the password has any lowercase letter.
  optional bool password_has_lowercase_letter = 25;

  // Deprecated since M80: Whether the password has any uppercase letter.
  optional bool password_has_uppercase_letter = 26 [deprecated = true];

  // Deprecated since M80: Whether the password has any digit.
  optional bool password_has_numeric = 27 [deprecated = true];

  // Whether the password has any special symbol.
  optional bool password_has_special_symbol = 28;

  // Noisified password length.
  optional uint32 password_length = 29;

  // If |password_has_special_symbol| is true, this field contains noisified
  // information about a special symbol used in a user-created password stored
  // in ASCII code.
  // Otherwise, this field is unset.
  optional uint32 password_special_symbol = 39;

  // The end of the section of password attributes.

  // Event observed by the password manager which indicated that the form was
  // successfully submitted. Corresponds to |mojom::SubmissionIndicatorEvent|.
  enum SubmissionIndicatorEvent {
    NONE = 0;
    HTML_FORM_SUBMISSION = 1;
    SAME_DOCUMENT_NAVIGATION = 2;
    XHR_SUCCEEDED = 3;
    FRAME_DETACHED = 4;
    DEPRECATED_MANUAL_SAVE = 5;  // obsolete
    DOM_MUTATION_AFTER_XHR = 6;
    PROVISIONALLY_SAVED_FORM_ON_START_PROVISIONAL_LOAD = 7;
    DEPRECATED_FILLED_FORM_ON_START_PROVISIONAL_LOAD = 8;            // unused
    DEPRECATED_FILLED_INPUT_ELEMENTS_ON_START_PROVISIONAL_LOAD = 9;  // unused
    PROBABLE_FORM_SUBMISSION = 10;
    CHANGE_PASSWORD_FORM_CLEARED = 11;
  }

  // The type of the event that was taken as an indication that the form has
  // been successfully submitted.
  optional SubmissionIndicatorEvent submission_event = 30;

  // The language of the page on which this form appears.
  optional string language = 31;

  // Form-level metadata observed by the client, randomized.
  optional AutofillRandomizedFormMetadata randomized_form_metadata = 32;

  // Information about a button's title (sync with another ButtonTitle in this
  // proto).
  message ButtonTitle {
    // Text showed on the button.
    optional string title = 1;

    // Describes how the button is implemented in HTML source.
    optional ButtonTitleType type = 2;
  }
  // Titles of form's buttons.
  // TODO(850606): Deprecate once randomized metadata is launched.
  repeated ButtonTitle button_title = 36;

  // Whether the fields are enclosed by a <form> tag or are unowned elements.
  optional bool has_form_tag = 37;

  // Captures whether or not this upload was a candidate for throttling.
  optional bool was_throttleable = 38;
}

Как это выглядит на самом деле

Раскукоженный пример

Погружаемся


0a   => 1   010 => id=1, type=length-delimited
        id  type

a703 => 1   010 0111| 000 0011 => 423 = length
        msb
     => 000 0011 ++ 010 0111 = 1 + 2 + 4 + 32 + 128 + 256

0a13 => length-delimited, id=1, size=0x13=19

4368726f6d652f38392e302e343338392e3836 => "Chrome/89.0.4389.86"

11   => 00010 001 => id=2, type=64bit

61 32 89 EA 99 6A 9E BA => 13447302746672280161

Encoding↗️

Как отправить запрос, когда есть proto файл?
  • Пишем text-formated сообщение
    
      days: 20
      ticker: "USDRUB"
                  
  • protoc --encode=demo.CurrencyRequest proto/currency.proto < currency_req.txt
  • Переводим в base64 и вставляем в Burp🥲
Как раздекодить запрос, когда есть proto файл?

protoc --decode=demo.CurrencyRequest proto/currency.proto < currency_req.http
          
Как составить запрос, когда нет proto файла?

Точно так же, только вместо названия ключей их номер


              1: 20
              2: "USDRUB"
            
⬇️

import blackboxprotobuf

print(blackboxprotobuf.encode_message({
    1: 20,
    2: "USDRUB"
}, {
    '1': {'type': 'int', 'name': ''},
    '2': {'type': 'bytes', 'name': ''},
}))
            
bytearray(b'\x08\x14\x12\x06USDRUB')
Как раздекодить запрос, когда нет proto файла?

protoc --decode_raw < currency_req.http
          

Шо там по бюрпу?

gRPC


syntax = "proto3";

package yandex.cloud.compute.v1;

service InstanceService {
  // Returns the specified Instance resource.
  //
  // To get the list of available Instance resources, make a [List] request.
  rpc Get (GetInstanceRequest) returns (Instance) {
    option (google.api.http) = { get: "/compute/v1/instances/{instance_id}" };
  }
  ...
}
          

Что делать, если нет proto файла?


service ServerReflection {
  // The reflection service is structured as a bidirectional stream, ensuring
  // all related requests go to a single server.
  rpc ServerReflectionInfo(stream ServerReflectionRequest)
      returns (stream ServerReflectionResponse);
}
message ServerReflectionRequest {
  string host = 1;
  // To use reflection service, the client should set one of the following
  // fields in message_request. The server distinguishes requests by their
  // defined field and then handles them using corresponding methods.
  oneof message_request {
    // Find a proto file by the file name.
    string file_by_filename = 3;

    // Find the proto file that declares the given fully-qualified symbol name.
    // This field should be a fully-qualified symbol name
    // (e.g. .[.] or .).
    string file_containing_symbol = 4;

    // Find the proto file which defines an extension extending the given
    // message type with the given field number.
    ExtensionRequest file_containing_extension = 5;

    // Finds the tag numbers used by all known extensions of the given message
    // type, and appends them to ExtensionNumberResponse in an undefined order.
    // Its corresponding method is best-effort: it's not guaranteed that the
    // reflection service will implement this method, and it's not guaranteed
    // that this method will provide all extensions. Returns
    // StatusCode::UNIMPLEMENTED if it's not implemented.
    // This field should be a fully-qualified type name. The format is
    // .
    string all_extension_numbers_of_type = 6;

    // List the full names of registered services. The content will not be
    // checked.
    string list_services = 7;
  }
}
            

reflection.proto↗️

grpcurl

GitHub↗️

            grpcurl -d '...' compute.api.cloud.yandex.net:443 yandex.cloud.compute.v1.InstanceService.List
          

          grpcurl compute.api.cloud.yandex.net:443 list
          

grpcui

GitHub↗️