Keniver's Blog

Protobuf 分析與重建案例分享

前言

這陣子在做 IoT 設備資安檢測的時候發現受測設備的設定檔是 Protobuf 的序列化格式,剛好先前在 使用 protobuf inspector 分析未知結構緩衝資料 一文中介紹了如何對未知結構的 Protobuf 進行分析,今天我們就來把分析結果轉成可用的程式吧!

文中的設定檔非當事設定檔,是依照概念與所發現的真實問題重新設計的檔案。

你可以在 Github – protobuf-analysis-sample 下載到本文用到的檔案。

0x00 使用 Protobuf inspector 分析資料格式

先照著 使用 protobuf inspector 分析未知結構緩衝資料 的說明對資料進行初步分析,可以發現結構意外單純。

透過上圖結果,我們可以觀察到以下資訊

項目 4,5 後方寫著 <chunk> = message: ,代表這兩個項目是指向另一個 message 封裝(可以把它當作另外一個物件看待)

0x01 安裝 Protobuf 編譯環境

這邊不再贅述,請參閱 在 macOS上安裝 Protobuf 編譯環境

0x02 重建 protobuf 結構描述檔

0x02.1 打地基

根據 步驟 0x00 的分析結果,我們大致上知道這個設定檔存在三個 Message,一個是根結構,兩個參考結構(網路設定/使用者帳號)

因此一開始我們可以先把基本架構蓋出來

File: config.proto

syntax = "proto2";

message NetworkSetting {
}

message Account {
}

message Config {
}

0x02.2 放入變數

message NetworkSetting {
    required string ip = 1;
    required string mask = 2;
    required string gateway = 3;
    required string dns1 = 4;
    optional string dns2 = 5;     # 在 0x00 的分析中並沒有這個項目,因為他是可選的,只有在設備設定了 DNS 2 後才會出現
}

message Account {
    required string username = 1;
    required string password = 2;
    required int32 level = 3;
}

message Config {      #這是我們的根結構
    required string config_version = 1;
    required string firmware_version = 2;
    required string device_identification = 3;
    required NetworkSetting network_setting = 4;
    repeated Account accounts = 5;      # 從 0x00 的分析中觀察到 項目 5 有複數個,因此設定為 repeated(陣列)
}

0x03 編譯 .proto 結構描述檔

完成描述檔後,便可以使用 protoc 將 .proto 轉換為我們想要使用的語言

編譯方法如下:

keniver@MacBook-Pro ~ $ protoc -I=`pwd` --python_out=`pwd` `pwd`/config.proto
#  --python_out 是因為我要編給 Python 使用,你也可以編譯給其他程式語言使用

編譯後會生成 檔案名稱_pb2.py 的檔案,這是供我們引入 Python 使用的物件

0x04 讀取 Protobuf 檔案

成功編譯 .proto 後,我們只需要在 Python 中引入編譯後的物件,就可以開始讀取 protobuf 的緩衝檔案

File: config_reader.py

import config_pb2   # 步驟 0x03 編譯後產生的物件

config = config_pb2.Config() #   在定義結構的階段,我們將 Config 定為根結構,故此處呼叫 Config 物件

with open("exported1.config") as f: 
    config.ParseFromString(f.read())  # 將讀進來的檔案轉換為物件

接著我們就可以依照我們的需求對檔案進行讀取

File: config_reader.py

print( config.accounts[0].username, config.accounts[0].password )
# 輸出為: admin admin
print( config.accounts[1].username, config.accounts[1].password )
# 輸出為: user user

請不要使用明文儲存使用者密碼,請用 Hash