├── .github └── FUNDING.yml ├── LICENSE.md ├── README.md ├── examples └── test.ser ├── pbd ├── __init__.py └── __main__.py ├── setup.cfg └── setup.py /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: rsc-dev 2 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Radoslaw Matusiak 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pbd - Protocol Buffers Disassembler 2 | 3 | [![PyPI](https://img.shields.io/pypi/v/pbd.svg)](https://pypi.python.org/pypi/pbd) 4 | [![Join the chat at https://gitter.im/rsc-dev/pbd](https://badges.gitter.im/rsc-dev/pbd.svg)](https://gitter.im/rsc-dev/pbd?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 5 | 6 | ## About 7 | Pbd is a Python module to disassemble serialized protocol buffers descriptors (https://developers.google.com/protocol-buffers/). 8 | 9 | Example: 10 | ```sh 11 | >python -m pbd -f examples\test.ser 12 | 13 | _|_|_| _| _| 14 | _| _| _|_|_| _|_|_| 15 | _|_|_| _| _| _| _| 16 | _| _| _| _| _| 17 | _| _|_|_| _|_|_| 18 | 19 | ver 0.9 20 | 21 | [+] Paring file test.ser 22 | [+] Proto file saved as .\test.proto 23 | >type test.proto 24 | // Reversed by pbd (https://github.com/rsc-dev/pbd) 25 | // Package not defined 26 | 27 | message Person { 28 | required string name = 1 ; 29 | required int32 id = 2 ; 30 | optional string email = 3 ; 31 | } 32 | ``` 33 | 34 | ## Installation 35 | ```sh 36 | pip install pbd 37 | ``` 38 | or 39 | ```sh 40 | python setup.py install 41 | ``` 42 | 43 | ## Usage 44 | ### API 45 | 46 | ```python 47 | import pbd 48 | 49 | input_file_name = 'test.protoc' 50 | 51 | proto = Pbd(input_file_name) 52 | proto.disassemble() 53 | proto.dump() 54 | ``` 55 | 56 | For multiple files with imports: 57 | ```python 58 | import os 59 | import pbd 60 | 61 | input_dir = 'input\\' # Input directory with serialized descriptors 62 | output_dir = 'output\\' # Output direcotry for proto files 63 | input_files = [f for f in os.listdir(input_dir) if os.path.isfile(os.path.join(input_dir, f))] 64 | 65 | proto = [] 66 | 67 | for f in files: 68 | p = Pbd(f) 69 | p.disassemble() 70 | proto.append(p) 71 | 72 | for p in proto: 73 | p.find_imports(proto) 74 | p.dump(output_dir) 75 | ``` 76 | 77 | 78 | ### Standalone module 79 | Check help for available options: 80 | ```sh 81 | python -m pbd -h 82 | ``` 83 | 84 | Disasm single file. 85 | ```sh 86 | python -m pbd -f test.ser 87 | ``` 88 | 89 | Disasm all files in given directory and fix imports. 90 | ```sh 91 | python -m pbd -i input_dir\ -o output_dir\ 92 | ``` 93 | 94 | ## License 95 | Code is released under [MIT license](https://github.com/rsc-dev/loophole/blob/master/LICENSE.md) © [Radoslaw '[rsc]' Matusiak](https://rm2084.blogspot.com/). 96 | -------------------------------------------------------------------------------- /examples/test.ser: -------------------------------------------------------------------------------- 1 | 2 | 3 | test.proto"1 4 | Person 5 | name (  6 | 7 | id ( 8 | email ( -------------------------------------------------------------------------------- /pbd/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | __author__ = 'Radoslaw Matusiak' 4 | __copyright__ = 'Copyright (c) 2016 Radoslaw Matusiak' 5 | __license__ = 'MIT' 6 | __version__ = '0.9' 7 | 8 | 9 | """ 10 | Protocol buffers disassembler. 11 | """ 12 | 13 | import os 14 | import sys 15 | 16 | from google.protobuf.descriptor_pb2 import FileDescriptorProto 17 | 18 | 19 | class Pbd(): 20 | """ 21 | Google protocol buffers disassembler. 22 | """ 23 | 24 | LABELS =[None, 'optional', 'required', 'repeated'] 25 | TYPES = [None, 'double', 'float', 'int64', 'uint64', 'int32', 'fixed64', 'fixed32', 'bool', 'string', 26 | 'group', 'message', 'bytes', 'uint32', 'enum', 'sfixed32', 'sfixed64', 'sint32', 'sint64'] 27 | 28 | def __init__(self, input_file): 29 | """Constructor. 30 | 31 | Keyword arguments: 32 | input_file -- input file name 33 | """ 34 | self.input_file = input_file 35 | self.lines = [] 36 | self.tabs = 0 37 | 38 | self.package = '' 39 | self.name = None 40 | 41 | self.defines = [] 42 | self.uses = [] 43 | self.imports = [] 44 | # end-of-method __init__ 45 | 46 | def _print(self, line=''): 47 | """Append line to internal list. 48 | Uses self.tabs to format indents. 49 | 50 | Keyword arguments: 51 | line -- line to append 52 | """ 53 | self.lines.append('{}{}'.format('\t'*self.tabs , line)) 54 | # end-of-method _print 55 | 56 | def _dump_enum(self, e, top=''): 57 | """Dump single enum type. 58 | 59 | Keyword arguments: 60 | top -- top namespace 61 | """ 62 | self._print() 63 | self._print('enum {} {{'.format(e.name)) 64 | self.defines.append('{}.{}'.format(top,e.name)) 65 | 66 | self.tabs+=1 67 | for v in e.value: 68 | self._print('{} = {};'.format(v.name, v.number)) 69 | self.tabs-=1 70 | self._print('}') 71 | # end-of-method _dump_enums 72 | 73 | def _dump_field(self, fd): 74 | """Dump single field. 75 | """ 76 | v = {} 77 | v['label'] = Pbd.LABELS[fd.label] 78 | v['type'] = fd.type_name if len(fd.type_name) > 0 else Pbd.TYPES[fd.type] 79 | v['name'] = fd.name 80 | v['number'] = fd.number 81 | v['default'] = '[default = {}]'.format(fd.default_value) if len(fd.default_value) > 0 else '' 82 | 83 | f = '{label} {type} {name} = {number} {default};'.format(**v) 84 | f = ' '.join(f.split()) 85 | self._print(f) 86 | 87 | if len(fd.type_name) > 0: 88 | self.uses.append(fd.type_name) 89 | # end-of-function _dump_field 90 | 91 | def _dump_message(self, m, top=''): 92 | """Dump single message type. 93 | 94 | Keyword arguments: 95 | top -- top namespace 96 | """ 97 | self._print() 98 | self._print('message {} {{'.format(m.name)) 99 | self.defines.append('{}.{}'.format(top, m.name)) 100 | self.tabs+=1 101 | 102 | for f in m.field: 103 | self._dump_field(f) 104 | 105 | for e in m.enum_type: 106 | self._dump_enum(e, top='{}.{}'.format(top, m.name)) 107 | 108 | for n in m.nested_type: 109 | self._dump_message(n, top='{}.{}'.format(top, m.name)) 110 | 111 | self.tabs-=1 112 | self._print('}') 113 | # end-of-method _dump_messages 114 | 115 | def _dump_method(self, m): 116 | """Dump single method. 117 | """ 118 | v = { 119 | 'name': m.name, 120 | 'input_type': ('stream ' if m.client_streaming else '') + m.input_type, 121 | 'output_type': ('stream ' if m.server_streaming else '') + m.output_type, 122 | } 123 | 124 | f = 'rpc {name}({input_type}) returns ({output_type}) {{'.format(**v) 125 | f = ' '.join(f.split()) 126 | self._print(f) 127 | self._print('}') 128 | 129 | self.uses.append(m.input_type) 130 | self.uses.append(m.output_type) 131 | # end-of-method _dump_method 132 | 133 | def _dump_service(self, s, top=''): 134 | """Dump single service type. 135 | 136 | Keyword arguments: 137 | top -- top namespace 138 | """ 139 | self._print() 140 | self._print('service {} {{'.format(s.name)) 141 | self.tabs+=1 142 | 143 | for m in s.method: 144 | self._dump_method(m) 145 | 146 | self.tabs-=1 147 | self._print('}') 148 | # end-of-method _dump_service 149 | 150 | def _walk(self, fd): 151 | """Walk and dump (disasm) descriptor. 152 | """ 153 | top = '.{}'.format(fd.package) if len(fd.package) > 0 else '' 154 | 155 | for e in fd.enum_type: self._dump_enum(e, top) 156 | for m in fd.message_type: self. _dump_message(m, top) 157 | for s in fd.service: self. _dump_service(s, top) 158 | # end-of-method _walk 159 | 160 | def disassemble(self): 161 | """Disassemble serialized protocol buffers file. 162 | """ 163 | ser_pb = open(self.input_file, 'rb').read() # Read serialized pb file 164 | 165 | fd = FileDescriptorProto() 166 | fd.ParseFromString(ser_pb) 167 | self.name = fd.name 168 | 169 | self._print('// Reversed by pbd (https://github.com/rsc-dev/pbd)') 170 | self._print('syntax = "proto2";') 171 | self._print('') 172 | 173 | if len(fd.package) > 0: 174 | self._print('package {};'.format(fd.package)) 175 | self.package = fd.package 176 | else: 177 | self._print('// Package not defined') 178 | 179 | self._walk(fd) 180 | # end-of-method disassemble 181 | 182 | def dump(self, out_dir='.'): 183 | """Dump proto file to given directory. 184 | 185 | Keyword arguments: 186 | out_dir -- dump directory. Default='.' 187 | """ 188 | uri = out_dir + os.sep + self.name 189 | with open(uri, 'w') as fh: 190 | fh.write('\n'.join(self.lines)) 191 | # end-of-method dump 192 | 193 | 194 | def find_imports(self, pbds): 195 | """Find all missing imports in list of Pbd instances. 196 | """ 197 | # List of types used, but not defined 198 | imports = list(set(self.uses).difference(set(self.defines))) 199 | 200 | # Clumpsy, but enought for now 201 | for imp in imports: 202 | for p in pbds: 203 | if imp in p.defines: 204 | self.imports.append(p.name) 205 | break 206 | 207 | self.imports = list(set(self.imports)) 208 | 209 | for import_file in self.imports: 210 | self.lines.insert(2, 'import "{}";'.format(import_file)) 211 | # end-of-method find_imports 212 | 213 | pass 214 | # end-of-class Pbd 215 | 216 | 217 | if __name__ == '__main__': 218 | pass 219 | -------------------------------------------------------------------------------- /pbd/__main__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | __author__ = 'Radoslaw Matusiak' 4 | __copyright__ = 'Copyright (c) 2016 Radoslaw Matusiak' 5 | __license__ = 'MIT' 6 | __version__ = '0.9' 7 | 8 | 9 | """ 10 | Protocol buffers disassembler. 11 | """ 12 | 13 | import argparse 14 | import os 15 | import sys 16 | 17 | from pbd import Pbd 18 | 19 | 20 | def logo(): 21 | print """ 22 | 23 | _|_|_| _| _| 24 | _| _| _|_|_| _|_|_| 25 | _|_|_| _| _| _| _| 26 | _| _| _| _| _| 27 | _| _|_|_| _|_|_| 28 | 29 | ver {} 30 | """.format(__version__) 31 | # end-of-function logo 32 | 33 | 34 | def main(): 35 | logo() 36 | parser = argparse.ArgumentParser(description='Protocol buffers disassembler.') 37 | input_group = parser.add_mutually_exclusive_group() 38 | input_group.add_argument('-f', '--file', help='input file') 39 | input_group.add_argument('-d', '--dir', help='input directory') 40 | 41 | parser.add_argument('-o', '--outdir', help='output directory, default=.', default='.') 42 | 43 | args = parser.parse_args() 44 | 45 | if args.file is None and args.dir is None: 46 | print '[!] Please specify input file or directory!' 47 | sys.exit(-1) 48 | 49 | if args.file is not None: 50 | print '[+] Paring file {}'.format(args.file) 51 | p = Pbd(args.file) 52 | p.disassemble() 53 | p.dump(args.outdir) 54 | print '[+] Proto file saved as {}'.format(os.path.join(args.outdir, p.name)) 55 | elif args.dir is not None: 56 | files = [os.path.join(args.dir, f) for f in os.listdir(args.dir) if os.path.isfile(os.path.join(args.dir, f))] 57 | 58 | proto = [] 59 | for f in files: 60 | print '[+] Paring file {}'.format(f) 61 | p = Pbd(f) 62 | p.disassemble() 63 | proto.append(p) 64 | 65 | print '[+] Fixing imports...' 66 | for p in proto: p.find_imports(proto) 67 | 68 | print '[+] Dumping files...' 69 | for p in proto: p.dump(args.outdir) 70 | # end-of-function main 71 | 72 | 73 | if __name__ == '__main__': 74 | main() -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup 2 | setup( 3 | name = 'pbd', 4 | packages = ['pbd'], 5 | version = '0.9', 6 | description = 'Pbd is a Python module to disassemble serialized protocol buffers descriptors (https://developers.google.com/protocol-buffers/).', 7 | author = 'Radoslaw Matusiak', 8 | author_email = 'radoslaw.matusiak@gmail.com', 9 | url = 'https://github.com/rsc-dev/pbd', 10 | download_url = 'https://github.com/rsc-dev/pbd/releases/tag/0.9', 11 | keywords = ['disassembler', 'pb2', 'protocol buffers', 'reverse'], 12 | classifiers = [ 13 | 'Development Status :: 4 - Beta', 14 | 'Environment :: Console', 15 | 'License :: OSI Approved :: MIT License', 16 | 'Natural Language :: English', 17 | 'Operating System :: OS Independent', 18 | 'Programming Language :: Python', 19 | 'Topic :: Software Development :: Disassemblers' 20 | ], 21 | ) --------------------------------------------------------------------------------