Burpsuite中protobuf数据流的解析 - Vincent
0×00 前言
對于protobuf over-HTTP的數據交互方式Burpsuite不能正確的解析其中的數據結構,需要Burpsuite擴展才能解析,筆者使用mwielgoszewski的burp-protobuf-decoder【1】擴展實踐了protobuf數據流的解析,供有需要的同學學習交流。筆者實踐使用的環境: burpsuite+python2.7+protobuf2.5.0。
0×01 安裝burp-protobuf-decoder擴展
burp-protobuf-decoder【1】擴展是基于protobuf庫(2.5.x版本)開發的burpsuite python擴展,可用于解析、篡改 request/response中protobuf數據流。從https://github.com/mwielgoszewski/burp-protobuf-decoder下載該擴展源碼,然后解壓。
該擴展是基于protobuf和jython實現的。先下載protobuf 2.5.0【2】源碼進行編譯,編譯方法請參考其README.txt文件。需求在burpsuite的Extender中配置Jython【3】的路徑:
Burpsuite中添加擴展:
在Burpsuite的Extender窗口中點擊“Add”按鈕,彈出的“Load Burp Extension”窗口中選擇如下信息:
然后Next,當看到如下信息時表示擴展加載成功:
Tips:
加載擴展時提示“Error calling protoc: Cannot run program "protoc" (in directory "******"): error=2, No such file or directory”錯誤
解決辦法:修改protoburp.py中調用protoc命令的路徑,有多處,如:
將process = subprocess.Popen(['protoc', '--version']中'protoc'改為'/home/name/protobuf/src/protoc'。
加載擴展碰到cannot import name symbol_database錯誤
可能是你使用的protoc與擴展所使用protobuf python庫版本不一致原因,一種解決辦法是下載protobuf 2.5.0源碼編譯后,修改protoburp.py中對應的路徑,再加載擴展。
擴展加載成功了,但不能解析protobuf數據流
該擴展通過判斷頭部“content-type”是否為“'application/x-protobuf'”來決定是否解析數據,你可以修改protoburp.py中的isEnabled()方法讓其工作。
0×02 protobuf簡介
protobuf是Google開源的一個跨平臺的結構化數據存儲格式。可用于通訊協議、數據存儲等領域的語言無關、平臺無關、可擴展的序列化結構數據格式。
protobuf通過定義“.proto”文件來描述數據的結構。.proto文件中用 “Message”來表示所需要序列化的數據的格式。Message由Field組成,Field類似Java或C++中成員變量,通常一個Field的定義包含修飾符、類型、名稱和ID。下面看一個簡單的.proto文件的例子:
#!cpp syntax = "proto2"; package tutorial; 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; }使用下面的python代碼生成二進制數據流:
#!python import addressbook_pb2 address_book = addressbook_pb2.AddressBook() person = address_book.person.add() person.id = 9 person.name = 'Vincent' person.email = 'Vincent@test.com' phone = person.phone.add() phone.number = '15011111111' phone.type = 2 f = open('testAb', "wb") f.write(address_book.SerializeToString()) f.close()序列化后的二進制數據流如下:
有關Protobuf的語法網上已有很多文章了,你可以網上搜索或參考其官網【4】說明。
2.1Varint編碼
Protobuf的二進制使用Varint編碼。Varint 是一種緊湊的表示數字的方法。它用一個或多個字節來表示一個數字,值越小的數字使用越少的字節數。這能減少用來表示數字的字節數。
Varint 中的每個 byte 的最高位 bit 有特殊的含義,如果該位為 1,表示后續的 byte 也是該數字的一部分,如果該位為 0,則結束。其他的 7 個 bit 都用來表示數字。因此小于 128 的數字都可以用一個 byte 表示。大于 128 的數字,比如 300,會用兩個字節來表示:1010 1100 0000 0010。
下圖演示了protobuf如何解析兩個 bytes。注意到最終計算前將兩個 byte 的位置相互交換過一次,這是因為protobuf 字節序采用 little-endian 的方式。
?(圖片來自網絡)
2.2數值類型
Protobuf經序列化后以二進制數據流形式存儲,這個數據流是一系列key-Value對。Key用來標識具體的Field,在解包的時候,Protobuf根據 Key 就可以知道相應的 Value 應該對應于消息中的哪一個 Field。
Key 的定義如下:
(field_number << 3) | wire_type
Key由兩部分組成。第一部分是 field_number,比如消息 tutorial .Person中 field name 的 field_number 為 1。第二部分為 wire_type。表示 Value 的傳輸類型。Wire Type 可能的類型如下表所示:
| 0 | Varint | int32, int64, uint32, uint64, sint32, sint64, bool, enum |
| 1 | 64-bit | fixed64, sfixed64, double |
| 2 | Length-delimi | string, bytes, embedded messages, packed repeated fields |
| 3 | Start group | Groups (deprecated) |
| 4 | End group | Groups (deprecated) |
| 5 | 32-bit | fixed32, sfixed32, float |
以數據流:08 96 01為例分析計算key-value的值:
#!bash 08 = 0000 1000b=> 000 1000b(去掉最高位)=> field_num = 0001b(中間4位), type = 000(后3位)=> field_num = 1, type = 0(即Varint) 96 01 = 1001 0110 0000 0001b=> 001 0110 0000 0001b(去掉最高位)=> 1 001 0110b(因為是little-endian)=> 128+16+4+2=150最后得到的結構化數據為:
1:150
其中1表示為field_num,150為value。
2.3手動反序列化
以上面例子中序列化后的二進制數據流進行反序列化分析:
#!bash 0A = 0000 1010b => field_num=1, type=2; 2E = 0010 1110b => value=46; 0A = 0000 1010b => field_num=1, type=2; 07 = 0000 0111b => value=7;讀取7個字符“Vincent”;
#!bash 10 = 0001 0000 => field_num=2, type=0; 09 = 0000 1001 => value=9; 1A = 0001 1010 => field_num=3, type=2; 10 = 0001 0000 => value=16;èˉ???–10??a?-—??|a€?Vincent@test.coma€?;
#!bash 22 = 0010 0010 => field_num=4, type=2; 0F = 0000 1111 => value=15; 0A = 0000 1010 => field_num=1, type=2; 0B = 0000 1011 => value=11;讀取11個字符“15011111111”;
#!bash 10 = 0001 0000 => field_num=2, type=0; 02 = 0000 0010 => value=2;最后得到的結構化數據為:
#!bash 1 {1: "Vincent"2: 93: "Vincent@test.com"4 {1: "15011111111"2: 2} } 手工反序列化最大難點是類型2,這個類型有的時候代表字符串,有的時候代表子元素,什么時候代表字符串什么時候代表子元素從已經經序列化的流里面不好分析(在沒有.proto文件的情況下),有的時候會混亂,推測遠原程序decode_raw是先測試下類型2下面的內容是否可以被解析為protobuf的子結構,如果可以則解析,如果不可以則解析為字符串,由此造成解析的不穩定性。(紅字標注的是自己的想法,并非原作者的話,正確性有待討論)2.4使用protoc反序列化
實現操作經常碰到較復雜、較長的流數據,手動分析確實麻煩,好在protoc加“decode_raw”參數可以解流數據,我實現了一個python腳本供使用:
#!python def decode(data):process = subprocess.Popen(['/usr/local/bin/protoc', '--decode_raw'],stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE)output = error = Nonetry:output, error = process.communicate(data)except OSError:passfinally:if process.poll() != 0:process.wait()return outputf = open(sys.argv[1], "rb") data = f.read() print 'data:\n',decode(data) f.close()使用python decode.py <proto.bin>即可反序列化,其中proto.bin為protobuf二進制數據流文件。得到結構化的數據后我們可以逐步分析,猜測每個Field的名稱,輔助協議、數據結構等逆向分析。
0×03 burpsuite+protobuf實戰
用webpy模擬protobuf over-HTTP的web app。
服務端overHttp_server.py內容如下:
#!python #!/usr/bin/env python #coding: utf8 #author: Vincent import web import time import osurls = ("/", "default",) app = web.application(urls, globals())class default:def GET(self):return 'hello world.'def POST(self):reqdata = web.data()print 'client request:'+reqdataresdata = reqdata.split(':')[-1]web.header('Content-type', 'application/x-protobuf')return resdata if __name__ == "__main__": app.run()客戶端overHttp_client.py內容如下:
#!python #!/usr/bin/env python #coding: utf8 #author: Vincent import urllib import urllib2 import json import addressbook_pb2 import sysproxy = 'http://<ip>:8888' target = "http://<ip>:8080/" enable_proxy = True proxy_handler = urllib2.ProxyHandler({"http" : proxy}) null_proxy_handler = urllib2.ProxyHandler({}) if enable_proxy: opener = urllib2.build_opener(proxy_handler) else: opener = urllib2.build_opener(null_proxy_handler) urllib2.install_opener(opener)def doPostReq():url = targetaddress_book = addressbook_pb2.AddressBook()f = open('testAb', "rb")address_book.ParseFromString(f.read())ad_serial = address_book.SerializeToString()f.close()data = ad_serialopener = urllib2.build_opener(proxy_handler, urllib2.HTTPCookieProcessor())req = urllib2.Request(url, data, headers={'Content-Type': 'application/x-protobuf'})response = opener.open(req)return response.read()resp = doPostReq() print 'response:',resp3.1proto文件逆向分析
啟動服務端:python overHttp_server.py <ip>:8080
客戶端請求:python overHttp_client.py
此時burp中已解析出protobuf數據,如下圖:
但是這個結構的可讀性還是比較差,我們可以通過逆向分析逐步猜測字段名稱、類型,然后再解析,方便實現協議的逆向、安全測試等。
對這個結構我們可以還原成以下proto文件:
#!cpp syntax = "proto2"; package reversed.proto1;message Msg {optional string _name = 1;optional int32 field2 = 2;optional string _email = 3;message subMsg1 {required string _phone = 1;optional int32 sub1_field2 = 2;}repeated subMsg1 field4 = 4; }message Root {repeated Msg msg = 1; }然后使用右鍵的“Load .proto”加載該文件:
再看解析結果:
3.2數據篡改
打開request攔截:
運行python overHttp_client.py發送請求。攔截到request后,把sub1_field2改為999。
“Forward”后看request數據,已被篡改:
0×04 參考
- 【1】https://github.com/mwielgoszewski/burp-protobuf-decoder
- 【2】https://github.com/google/protobuf/tree/v2.5.0
- 【3】https://wiki.python.org/jython/InstallationInstructions
- 【4】https://developers.google.com/protocol-buffers/docs/proto
- 【5】https://www.ibm.com/developerworks/cn/linux/l-cn-gpb/
- 【6】https://developers.google.com/protocol-buffers/docs/overview
- 【7】http://www.tssci-security.com/archives/2013/05/30/decoding-and-tampering-protobuf-serialized-messages-in-burp/
總結
以上是生活随笔為你收集整理的Burpsuite中protobuf数据流的解析 - Vincent的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: RC4算法实现
- 下一篇: protobuf流的反解析Message