BLACKBOXPROTOBUF(3)

πŸ“¦ version 1.4.2, in ubuntu stonking

πŸ’… BLACKBOXPROTOBUF πŸ’…

ok so like. this is a manual page from the Python Library Functions section, and bestie, it's a good one. buckle up. πŸ¦‹


✨ NAME ✨

blackboxprotobuf β€” decode and re-encode Protocol Buffer messages without a .proto descriptor. no descriptor? no problem. πŸ’β™€οΈ


🌸 SYNOPSIS 🌸

import blackboxprotobuf

message, typedef = blackboxprotobuf.decode_message(data)
data = blackboxprotobuf.encode_message(message, typedef)

that's it. that's the whole vibe. ✨


πŸŽ€ DESCRIPTION πŸŽ€

blackboxprotobuf is a Python module for decoding and re-encoding Protocol Buffer (protobuf) messages when you do not have access to the original .proto descriptor file. it's giving "found this mysterious binary blob, must understand it." πŸ’€

it's made for reverse engineering and penetration testing situations β€” you know the ones β€” where the protobuf definitions are literally not available but you have to inspect or modify the messages anyway. lowkey iconic honestly.

the library gives you two whole ways in:

  • a programmatic Python interface 🐍
  • a command-line interface (go check out bbpb(1), i'll wait) πŸ’»

🧚 BACKGROUND 🧚

Protocol Buffers are a binary serialization format made by Google. messages get defined in .proto files that both the sender and the receiver share. cute, right?

here's the plot twist though: serialized protobuf messages do not contain field names or complete type information. each field only carries:

  • a field number
  • a wire type

the wire type defines how the field length gets interpreted β€” which means unknown fields can still be skipped. slay. 🫢

there's also the protoc compiler, which has a --decode_raw option for decoding protocol buffers without the definition. but β€” and this is the important part β€” it does not support reconstructing and re-encoding modified payloads. so it can look but it can't touch. 😭

blackboxprotobuf fills that gap. main character energy, fr fr.


πŸ”₯ HOW IT WORKS πŸ”₯

the library does best-effort type inference based on:

  • the wire type
  • field content

then it constructs a type definition (a "typedef") that can later be used to re-encode the message. so you decode once, and you keep the keys. πŸ”‘

and because guessing is guessing, users may override the guessed types with custom type definitions β€” and assign human-readable field names. treat yourself. πŸ’–


πŸ‘‘ CORE API πŸ‘‘

  • decode_message(data, typedef=None, config=None) β€” decodes a protobuf byte string. returns a tuple: (message_dict, generated_typedef). if you don't hand it a typedef, all field types get guessed. rolling the dice bestie 🎲
  • encode_message(message_dict, typedef, config=None) β€” encodes a message dictionary using the type definition you provide. heads up: all fields have to be defined in the typedef or it's not happening. no cap.
  • protobuf_to_json(data, typedef=None, config=None) β€” convenience wrapper so you can decode into JSON-compatible structures. the lazy-girl route, respectfully. πŸ’…
  • protobuf_from_json(json_obj, typedef=None, config=None) β€” encode from a JSON-compatible representation. the other direction. we love a round trip. πŸ”

🌈 TYPE DEFINITIONS 🌈

type definitions are Python dictionaries that describe the message structure. each entry works like this:

  • key = field number (an integer)
  • value = a dictionary containing at minimum:
    • "type" : a string type identifier

the valid type identifiers live in:

blackboxprotobuf.lib.types.type_maps

and message fields must also contain one of these two:

  • "message_typedef"
  • "message_type_name"
⚠️ if both are provided, message_type_name gets ignored. only one of them is getting the spotlight.

πŸ“ JSON CONVENIENCE FUNCTIONS πŸ“

the JSON helpers protobuf_to_json and protobuf_from_json automatically:

  • sort output fields
  • improve readability of bytes
  • annotate example values in the typedef

these two are literally designed for user-facing workflows. they're the presentable ones. πŸŽ€


πŸ’œ PROTOFILE IMPORT/EXPORT πŸ’œ

  • import_protofile(filename) β€” converts a .proto file into a blackboxprotobuf type definition. glow-up complete. ✨
  • export_protofile(typedef, filename) β€” exports a type definition out to a .proto file.

limitations (read these, bestie, they matter):

  • not a complete protobuf parser
  • does not automatically resolve import statements
  • imported files must be manually processed first

⭐ TYPE VALIDATION ⭐

  • validate_typedef(typedef, previous_typedef=None) β€” validates the internal consistency of a type definition you've been messing with.

it detects:

  • incompatible type changes
  • duplicate field names
  • structural inconsistencies

basically it's the friend who tells you your outfit has a stain. helpful. honest. slightly devastating. 😭


🧁 HELPER FUNCTIONS 🧁

  • json_safe_transform() β€” encodes/decodes bytes using latin1 for readable JSON output.
  • sort_output() β€” sorts decoded message fields numerically.
  • sort_typedef() β€” sorts typedef fields for improved readability.

little guys. doing little jobs. obsessed. 🫢


πŸ’– CONFIGURATION πŸ’–

most API functions accept a config parameter of type:

blackboxprotobuf.lib.config.Config

this replaces the earlier global variables. we do not do globals around here anymore, periodt.

config options include:

  • known_types β€” a mapping of message type names to typedefs.
  • default_binary_type β€” the default type for unknown binary fields. Default: "bytes". Alternatives: "bytes_hex", and a future "bytes_base64". πŸ‘€
  • default_types β€” override the default wire-type mappings. example:
default_types[WIRETYPE_VARINT] = "uint"
if no config is provided, the global default configuration object is used. so you always get something.

🌸 WIRE TYPE BREAKDOWN 🌸

πŸ”’ Variable Length Integers (varint)

used for:

  • integers
  • booleans
  • enums

supported interpretations:

  • uint β€” unsigned
  • int β€” signed (inefficient for negatives)
  • sint β€” ZigZag encoding (efficient negatives)

default: int

πŸ“ Fixed32 / Fixed64

fixed-size integers or floating point values. default interpretation: floating point. 🫧

πŸ“¦ Length Delimited

used for:

  • strings
  • bytes
  • nested messages
  • packed repeated fields

default for unknown binary fields: string/bytes.

⚠️ packed repeated fields are difficult to distinguish from arbitrary byte strings and may require manual correction. it's not a flaw, it's a character trait. πŸ™ˆ

🎁 PACKED REPEATED FIELDS 🎁

history lesson, stay with me:

  • in protobuf v2, repeated fields had to be explicitly declared packed.
  • in protobuf v3, they are packed by default.

and because packed fields omit per-element tags, correct parsing often requires user intervention. so it's a you problem now, but like, lovingly. πŸ’œ


✨ EXAMPLE ✨

import blackboxprotobuf
import base64

data = base64.b64decode('KglNb2RpZnkgTWU=')
message, typedef = blackboxprotobuf.decode_message(data)

message[5] = "Modified Me"

new_data = blackboxprotobuf.encode_message(message, typedef)

decode it. poke it. change field 5. re-encode it. iconique. πŸ’…


πŸ‘€ SEE ALSO πŸ‘€


πŸ“œ LICENSE πŸ“œ

Expat


Copyright (c) 2018-2026 NCC Group Plc


🫢 MAN PAGE 🫢

Enkelena Haxhija (EnkelenaH) created this man page based on the upstream README. (we stan a documentation queen πŸ‘‘)


and that's a wrap, bestie! this page is part of blackboxprotobuf 1.4.2, dated February 2026 β€” go decode something weird, you've got this. πŸ’–βœ¨πŸ¦‹

πŸ“¦ where this page comes from