星期三, 4月 15, 2020

針對 Android 裝置的詐騙簡訊攻擊

今天早上才收到中華電信的警告簡訊:
結果下午就真的收到很可疑的簡訊:

看到這麼可疑的東西,當然要把網址丟到 wget 去研究看看到底在搞什麼:
果然,該網址轉址之後的網頁內的 JavaScript 在偵測到應該是在 Android 系統的瀏覽器被開啟後,直接把瀏覽器的網址指向很可疑的「chrome.apk」下載網址。把該 APK 上傳到 VirusTotal 去檢查,果然是惡意 app:
使用 Android 的朋友,如果有開啟允許安裝不明來源的應用程式的安全性選項的話,要特別小心這類的詐騙簡訊。

繼續閱讀全文

星期一, 9月 03, 2012

為 Das U-Boot 新增網路服務

最近被交代要在 u-boot 上實作 DHCP server。雖然單純的寫一個簡單的 DHCP server 其實蠻容易的,只要完成 "DORA" (Discover, Offer, Request, Ack) 即可,sourceforge 上也有一些程式碼可以參考,例如 Simple Embedded DHCP 等等,但是要在 U-Boot 上實作就不是那麼直接了,因為在 U-Boot 環境中沒有 OS 的協助,所以必須依照 U-Boot 的架構從零開始。

今天完成了可運作的 DHCPD 實作,雖然還有一些細節需要再檢驗 U-Boot 原始碼來做些確認和釐清,但大致上的重點應該都有抓到,所以紀錄一下以供以後參考。不過因為我的目標 U-Boot 版本是大約 2005 年釋出的 v1.1.4,雖然大原則相同,但一些細節可能會有所不同,例如定義一些函式、字串、巨集的檔案可能會有所不同等等。


U-Boot 網路相關的核心功能與函式,主要是在 <U-BOOT>/net/net.c 中實作,並且在 <U-BOOT>/include/net.h 中定義許多相關全域變數。基本上要實作任何網路相關功能,大概至少要引入 net.h 和 common.h:

#include <net.h>
#include <common.h>
此外,若實作的服務是原本 U-Boot 中沒有的服務,那多半還需要在 net.h 的 proto_t 中定義新的項目。

U-Boot 中的網路功能,主要是透過 net.c 中的 int NetLoop(proto_t protocol) 來執行。當 NetLoop() 函式開始執行時,會根據傳入的 protocol 作該協定相關的初始化工作,然後再呼叫 net_check_prereq(protocol) 來做該服務相關的先決條件檢查,如果檢查通過的話,通常就會執行該相關服務的起始函式,接著進入無限迴圈,在每次環圈執行中檢查全域變數 NetState 來決定接下來是繼續執行迴圈還是結束 NetLoop() 的執行。

在這樣的架構下,要實作一個新的服務(EX. MYSERVICE),需要至少實作以下的部分:
  • 至少需要實作新服務的起始函式 MyServiceStart()、封包處理回呼函式 MyServiceHandler()、逾時處理回呼函式 MyServiceTimeout() 等,其中除了 MyServiceHandler() 的原型會有輸入參數以外,其餘兩個函式都可以沒有輸出輸入參數。另外,通常還會實作一個封包建構和傳送函式 MyServiceSend(),把封包建構的程式碼由 MyServiceHandler() 中分離出來,方便修改除錯。此外,因為大部分的函式都沒有使用輸入參數來做參數傳遞,因此通常還會建立一個該服務的狀態全域變數 MyServiceState 來追蹤新服務的執行階段。
  • 在 <U-BOOT>/include/net.h 的 proto_t enum 中新增該服務
  • 修改 <U-BOOT>/net/net.c 的 NetLoop() 中協定初始化的 switch() {...},加入新服務的初始化程式碼,例如由環境變數中把 local IP 填入 NetOurIP 全域變數等等。
  • 修改 net_check_prereq() 函式,加入新服務的檢查。
  • 修改 NetLoop() 中的 swtich(net_check_prereq(protocol)) {...},在 "case 0:" 的部分,加上新服務在通過檢查後的後續初始化程式碼,並在最後呼叫新服務的 MyServiceStart() 實際起始新服務。

至於新服務的3個基本函式要包含怎樣的內容呢?大致上來說,MyServiceStart() 需要處理以下工作:
  • 開始接收封包前的其他初始化工作。
  • 使用 NetSetTimeout(TIMEOUT, MyServiceTimeout()) 來註冊新服務的逾時時間門檻以及逾時處理回呼函式。
  • 使用 NetSetHandler(MyServiceHandler()) 來註冊新服務的封包接收處理回呼函式。

MyServiceHandler() 則需要負責接收到封包後的處理,例如封包內容的解讀、根據收到的封包更新 MyServiceState 狀態變數、呼叫 MyServiceSend() 來建構和發送回應封包、根據服務的執行階段設定 NetState 全域變數來結束 NetLoop() 的執行或呼叫 NetStartAgain() 重新初始化 NetLoop() 的執行等等。

至於 MyServiceTimeout() 中,則是處理逾時的狀況,例如顯示除錯訊息後,把 NetState 設定為 NETLOOP_FAIL 讓 NetLoop() 結束執行等等。不過要注意一點的是,如果在接收到有效封包後沒有在 MyServiceHandler() 中呼叫 NetSetTimeout(0, (thand_f *)0) 取消逾時回呼的註冊,或者是重新設定 Timeout 時限值 (即 NetSetTimeout() 的第一個參數)的話,逾時的計算會繼續執行,所以如果一開始的 Timeout 值設定得很短,然後又忘了在之後重設 Timeout 的話,很可能會發生服務執行到一半莫名其妙被中斷的 bug。

除了實作這3個基本函式以外,通常還會把封包建構和傳送的程式碼,集中在 MyServiceSend() 函式中。在 U-Boot 中要建構封包,幾乎是必須從零開始。net.c 中有提供兩個封包傳送函式,發送網路層封包的 NetSendPacket() 函式,以及發送傳輸層 UDP 封包的 NetSendUDPPacket() 函式,需要視新服務所用協定的封包種類做選擇。封包的建構通常是宣告一個 uchar *pkt 指標後,由 pkt=NetTxPacket 取得外送封包的指標,接著就是逐步加上 ethernet header、IP header、負載協定封包的檔頭與資料等等,填值時需注意適當使用 htons()、htonl() 等函式來處理 byte order 的問題。

到此為止,新服務的實作大致完成了,但是如果想要在 U-Boot 的互動式命令列介面中能夠用指令來執行這個新服務,則還需要實作一個新的 U-Boot 命令列指令。

U-Boot 的命令列指令的實作,都集中在 <U-BOOT>/common/cmd_*.c 中,並且要在 <U-BOOT>/include/cmd_confdefs.h 中定義該指令的設定用旗標值的巨集,以及修改相關的 Makefile 中把新指令和新服務的 .o 檔加入連結的目標中。

要實作一個新的命令列指令 myservice,大致上需要修改以下幾個地方:
  • 在 <U-BOOT>/include/cmd_confdefs.h 中定義 CFG_CMD_MYSERVICE 巨集。U-Boot 在設定命令列介面要包含哪些指令的 CONFIG_COMMAND 巨集時,會使用這個巨集的旗標值作 OR 運算,來決定要包含的指令有哪些。如果找不到適當的值的話,就只好取代已經被定義但應該用不到的指令的旗標值。
  • 在 <U-BOOT>/common/cmd_net.c 中,實作 do_myservice() 函式,並使用 U_BOOT_CMD(myservice, 1, 1, do_myservice, "description", "Help Message") 巨集在指令表中註冊 myservice 指令與對應的 do_myservice() 處理函式。至於 do_myservice() 處理函式的定義,最簡單的方式就是直接呼叫 cmd_net.c 中定義的 netboot_common() 函式,該函式會在完成參數分析之後,呼叫 NetLoop() 函式開始執行新服務。

假如所有實作都正確,且 CONFIG_COMMAND 巨集中有啟用 CFG_CMD_MYSERVICE 的話,在進入 U-Boot 的命令列模式後,使用 help 指令,應該會看到 myservice 指令也在其中。

大致上要實作一個新的網路服務,並且要新增一個命令列指令來啟動該服務的注意事項就是這些,剩下的就是一些實際實作時隨情況而定的細節,以及不斷的除錯直到新服務可以順利執行了。過幾天如果時間許可,也許會寫一個簡單的 echo service 來紀錄整個實作的細節。

繼續閱讀全文

星期四, 8月 30, 2012

Linux 2.6.13+ 的嵌入式系統上實現 USB 熱插拔

某個我習慣用來做紀錄的 BBS 系統因為硬體故障,所以暫時無法使用,所以還是寫篇文章紀錄起來好了。

最近因為工作上的需要接觸了些嵌入式系統的開發工作。基於設計需求,必須讓系統支援 USB 裝置的熱插拔。如果只是單純的 USB thumb drive 的熱插拔其實問題不大,但是如果接上去的裝置是個 USB 的記憶卡讀卡機的話,狀況就複雜了些。為了解決讀卡機的熱插拔問題,花了點時間研究了一下以 udev 為基礎的熱插拔機制到底是怎麼運作的。


以 Ubuntu Linux 為例,Kernel 2.6.13+ 的 Linux 系統目前的熱插拔機制,主要是靠 kernel 的 udev 機制,搭配 udev 軟體套件和 udisks 套件,來動態管理 /dev 下的裝置檔案以及實現 USB 熱插拔。根據這份有點稍微過時的文件,目前 kernel 的 uevent 介面,提供了兩種由 user space 實現熱插拔的機制,一個是透過在 /sys/kernel/uevent_helper (或 /proc/sys/kernel/hotplug) 設定一個 user space 的 helper 程式,在 uevent 觸發時執行;另一個則是透過 netlink 由 user space daemon 來接收 uevent。

在像是 Ubuntu Linux 之類的 Desktop 系統上,目前常見的熱插拔,是 udevd 透過 netlink 接收 uevent 介面送出的熱插拔事件,然後 udevd 根據 rule 分別在 /dev 下建立對應的裝置檔案或觸發其他程式,例如透過 dbus 通知 GNOME 之類的桌面環境有新裝置出現等等。在嵌入式系統上,最常見的替代方案,則是使用 busybox 中的 mdev 當 uevent_helper 來取代 udevd。mdev 在 uevent 觸發執行時,會根據 /etc/mdev.conf 中設定的規則來建立和管理 /dev 下的裝置檔案,也能用來呼叫其他的程式進行像是自動掛載等動作。不過如果插入的裝置是 USB 讀卡機的話,整個狀況又不太一樣了。

USB 讀卡機如果在接上 USB 插槽前,讀卡機中沒有記憶卡的話,插上去之後透過 uevent_helper 介面可以發現 kernel 偵測到 raw device,但是之後再插入記憶卡的話,從 dmesg 的輸出中會看到 kernel 完全沒有任何反應。使用一個簡單的 shell script 取代 mdev 來檢視整個 uevent 觸發的狀況,以 env 把 uevent 傳入的環境變數內容全部紀錄下來,可以發現確實在讀卡機插入 USB 插槽時,會觸發一連串的 uevent,但是之後插卡時則完全沒有任何 uevent 發生。使用 kernel 的 usbmon 搭配 debugfs 監測 USB 控制器的通訊狀態,可以發現問題出在讀卡機在記憶卡插上去之後,並沒有和 USB Host 有任何通訊產生。

那到底 Desktop 系統中的記憶卡熱插拔又是怎樣辦到的呢?檢查 udev 的 rule 可以發現,udevd 根據這些 rule,由 vendor ID 和 product ID 等資訊,以及透過 usb_id 等輔助程式查詢,確認是讀卡機類型的裝置之後,會設定 ID_DRIVE_FLASH_* 等環境變數,通知 udisks 這個裝置是讀卡機裝置,然後再由 udisks 定時對讀卡機的 raw device 做讀取,也就是做所謂的 polling 的動作。當記憶卡插入讀卡機後,polling 的動作會讓 kernel 偵測到有 partition 裝置,並又觸發後續的 uevent。

因此,要在嵌入式裝置上實現讀卡機的熱插拔,最直接的方式自然是實作類似於 udevd 和 udisks 的裝置類型判斷以及裝置 polling 的機制。不過問題是要移植 udisks 和 usb_id 等輔助程式到資源有限的嵌入式系統上,似乎不是個理想的做法。因此我的變通辦法是,透過 mdev 在 sd[a-z] 的 raw device 建立時呼叫一個輔助判別程式,這個判別程式則是使用 uevent 的 DEVPATH 中的 SCSI host、channel、ID、LUN 等資訊,在 /proc/scsi/scsi 的裝置列表中找到對應的裝置內容,並比對 "Model" 資訊來判斷插入的 raw device 是否是讀卡機,如果不是的話,則串聯呼叫自動掛載程式;如果發現是讀卡機的話,則啟動另一個 poller daemon 程式對該裝置定時做 polling 的動作,來處理卡片插入和移除的事件,直到裝置移除時,才由輔助判別程式通知 poller daemon 裝置已移除,讓 daemon 結束執行。

這樣的解決方案也許不是最佳方案,但是應該是相對簡單的解決方法,至少在目標系統上,我使用 busybox 的 ash,用 shell script 就實作了驗證這個想法用的 tester 程式和 pollerd 程式,成功的以軟體的方式達成 USB 熱插拔。


繼續閱讀全文

星期六, 4月 07, 2012

使用 Shell Script 進行 URL 編/解碼

一段時間之前,我寫了個利用 GNU coreutils 中的 "printf" 程式來進行 URL 解碼的 script 叫作 "urldecode"。今天本來想寫個使用 tinyurl.com 來產生短網址的 script,不過這樣的 script 必須要用到 URL 編碼來把傳給 tinyurl 的網址資料先編碼才行。在查了維基百科的 "Percent-encoding" 頁面之後,我寫了個用來做 URL 編碼的 script 叫 "urlencode


首先來看解碼的部份。解碼是相對簡單的事情,因為 "printf" 接受 "\xHH" 這樣的格式字串("HH" 是一位或兩位數的16位元數值),所以只要先把編碼字串中的 '%' 換成 "\x" 字串後,再整個丟給 "printf" 即可。以下是我的實作:


#!/bin/bash
#
# urldecode - decoding the URL-encoded string
#
# (C)2010 Shang-Feng Yang <storm_DOT_sfyang_AT_gmail_DOT_com>
#
# License: GPLv3

ENC_STR=$@
[ "${ENC_STR}x" == "x" ] && {
TMP_STR="$(cat - | sed -e 's/%/\\x/g')"
} || {
TMP_STR="$(echo ${ENC_STR} | sed -e 's/%/\\x/g')"
}
PRINTF=/usr/bin/printf
exec ${PRINTF} "${TMP_STR}\n"

"urldecode" 程式接受由標準輸入串流中或從命令參數中傳入目標字串。不過這個程式有個明顯的問題,那就是因為是把前處理過的字串整個丟給 "printf" 當作格式字串,所以當輸入字串太長時,程式會發生錯誤。

至於編碼的部份就相對比較麻煩了。我最初的想法是掃描輸入字串找出保留字元,把這些字元轉換後,再代換進輸入字串中。為了做字元轉換,我寫了個叫作 "char2hex" 的 script 來把目標字元轉換成對應的 ASCII 碼的 16 進位數值:


#!/bin/bash
#
# char2hex - returning the hexadecimal value of the given characters
#
# (C)2012 Shang-Feng Yang <storm_DOT_sfyang_AT_gmail_DOT_com>
#
# License: GPLv3

function usage() {
echo -e "Usage:\n"
echo -e "\t$(basename $0) CHARACTER(S)_TO_CONVERT\n"
}

CHAR=$1

[ "x${CHAR}" == "x" ] && { usage; exit 1; }

echo -n "${CHAR}" | od -A n -t x1 | tr -d ' '

這個 script 的運作方式相當直觀,唯一需要稍微提一下的是 "echo" 需要加上 '-n' 選項的原因。"echo" 預設會在輸出的最後加上一個換行字元,所以如果不加 '-n' 選項的話,輸出的結果在結尾會多個 "0a" 在後面。加上 '-n' 選項會關閉這個預設行為。

這種方法似乎相當簡單,但是實際上實作可能就不是那麼容易了。也許是我太笨想不到解法,不過像是光要從輸入字串中挑出保留字元,我就想不到有什麼簡單又不會因為大幅度增加 I/O 導致程式跑得很沒效率的方法。對像我這種懶惰的人來說,為了這個不是主要目的的問題想破頭,顯然不是個可行的辦法。

在看過維基百科 "Percent-encoding" 頁面的 "Percent-encoding reserved characters" 和 "Percent-encoding the percent character" 兩個段落後,發現必須進行編碼的保留字元其實並不多,所以最直接簡單的辦法是使用相對無腦的『列表法』來實作:


#!/bin/bash
#
# urlencode - escaping the reserved characters using URL-encoding
#
# (C)2012 Shang-Feng Yang <storm_DOT_sfyang_AT_gmail_DOT_com>
#
# License: GPLv3

STR=$@
[ "${STR}x" == "x" ] && { STR="$(cat -)"; }

echo ${STR} | sed -e 's| |%20|g' \
-e 's|!|%21|g' \
-e 's|#|%23|g' \
-e 's|\$|%24|g' \
-e 's|%|%25|g' \
-e 's|&|%26|g' \
-e "s|'|%27|g" \
-e 's|(|%28|g' \
-e 's|)|%29|g' \
-e 's|*|%2A|g' \
-e 's|+|%2B|g' \
-e 's|,|%2C|g' \
-e 's|/|%2F|g' \
-e 's|:|%3A|g' \
-e 's|;|%3B|g' \
-e 's|=|%3D|g' \
-e 's|?|%3F|g' \
-e 's|@|%40|g' \
-e 's|\[|%5B|g' \
-e 's|]|%5D|g'

"urlencode" 的內容相當簡單不需要多做解釋。這個 script 同樣可以使用 STDIN 資料流和命令列參數的方式來傳入輸入字串。下面是使用這兩個 script 的一些範例:


$ urlencode http://en.wikipedia.org/wiki/Percent-encoding
http%3A%2F%2Fen.wikipedia.org%2Fwiki%2FPercent-encoding
$ echo 'http://en.wikipedia.org/wiki/Percent-encoding' |urlencode
http%3A%2F%2Fen.wikipedia.org%2Fwiki%2FPercent-encoding
$ urldecode $(urlencode http://en.wikipedia.org/wiki/Percent-encoding)
http://en.wikipedia.org/wiki/Percent-encoding
$ urlencode http://en.wikipedia.org/wiki/Percent-encoding |urldecode
http://en.wikipedia.org/wiki/Percent-encoding

註:由於應 blogger 要求更新了 blogger 底層的 template,所以上面的程式碼和終端機區塊的排版有點問題...我未來如果有足夠動力的話也許會去改 CSS 把這個問題修好...

繼續閱讀全文

星期三, 11月 09, 2011

可憐的暴龍

這篇其實是針對我之前抱怨現在很多人翻譯別人的圖片或其他創作時,有些人刻意忽略標註來源的基本禮貌。

老美蠻常開暴龍 (T-Rex) 的短前肢的玩笑。下面這個胸章就是其中一例。


以下圖片引用自 9gag.com,最原始來源雖然做過搜尋,但發現難以查證。


以下是我翻譯後的圖片:


使用軟體:GIMP

繼續閱讀全文

星期四, 11月 03, 2011

翻譯或引用時應該註明來源出處

網路上有很多資訊是可以免費取得的,有些沒有註明授權方式,有些則註明使用 Creative Commons 授權。不過不管哪一種,引用別人的創作時,不論是直接引用或是間接的翻譯內容時,註明原始內容的來源出處在我看來是常識性的應有禮貌,但是似乎對其他人來說,似乎不是這麼回事?


前一陣子,PTT 電子佈告欄上開始出現了翻譯國外網路漫畫或圖片的風潮,霎時間突然出現超過一位以上的翻譯者,其中還有因為翻譯相同漫畫而發生磨擦的狀況。不過老實說,在我看來,不管是哪位翻譯者,幾乎都發生沒有註明出處的問題。有的翻譯者翻譯的圖片是經由某些收集有趣圖片的網站而來的,而該網站已經在圖片上加註他們網站的網址浮水印(不過還是沒有圖片原始來源的網址),因此『勉強』算是標上了來源,馬馬虎虎算是可以接受。不過最糟糕的是那些直接翻譯自原始來源網站的圖片,但又不標上來源網址的。有些圖片經過不斷被轉錄之後,要追查來源確實是幾乎不可能,但是會造成這種現象的主因,根本也是在於轉錄時沒有標明來源所造成的結果!

引用或翻譯時註明來源不但是一種基本禮貌,同時也是一種避免翻譯失真的補救辦法。翻譯並不難,但是要『翻得對』有時候不是那麼容易,要『翻得好』很多時候更是難上加難。註明來源出處,除了是一種基本禮貌,也是給予原始創作人應得的讚賞之外,更是給予讀者一個得知創作品原意的補救途徑。畢竟很少有人可以拍胸脯說,自己可以原汁原味的表現出原文的原意,不是嗎?

繼續閱讀全文

星期二, 11月 01, 2011

ABC 的 Once Upon A Time

今年底,ABC 和 NBC 不約而同的推出以迪士尼童話故事以及格林童話為背景的影集。ABC 的是 Once Upon A Time,NBC 則是 Grimm。ABC 的 Once Upon A Time 是以白雪公主的故事為背景來展開劇情,目前已經播出兩集了。


Once Upon A Time 雖然是以白雪公主為主軸,但是其實還參雜了許多其他故事裡的角色,像是睡美人等等,故事劇情其實在一開始就跟童話故事有些不同。

女主角之一是由在 House 中飾演 Dr. Cameron 的 Jennifer Morrison 擔任。自從 Jennifer Morrison 在上一季中退出 House 的演員陣容之後,已經一段時間蠻看到人了,主要是她有出現的幾個影集我都沒在看。到第二集為止,雖然 Jennifer Morrison 出現過幾個蠻不錯的造型,可是總覺得怪怪的,而且看起來比飾演他『理論上』的媽媽的 Ginnifer Goodwin 看起來要老氣(不過演員的實際年齡來說,確實 Ginnifer Goodwin 比 Jennifer Morrison 大一歲,但畫面上看起來的感覺則有點相反)。

男性反派角色之一則有帥氣的蘇格蘭演員 Robert Carlyle 某反派角色。Robert Carlyle 演過的電視電影其實我看過的不只一部,但是讓我對這個演員有印象的,反而是 Stargate 系列中唯一被腰斬的 Stargate Universe。Stargate Universe 中,他演的角色也是個不是完全正派的科學家,但是他的角色是我認為在編劇頭殼壞掉的 Stargate Universe 中,最『正常』最『合乎邏輯』的角色,也是我在那系列中最喜歡的角色。相對於其他角色的反反覆覆,他演的角色其實最終的動機相當單純,是純粹的對終極知識的渴望,雖然很多時候因為這種渴望而做出不合人情的判斷,但是至少是從一而終的角色。

至於其他演員,大都不是我熟悉的演員,雖然有一些以前看過,不過比起 Jennifer Morrison 和 Robert Carlyle 來說,都算是不熟悉的演員,不過幾個主要演員其實都還表現得不錯。像是飾演反派女主角的 Lana Parrilla 雖然『童話造型』其實不好看,但是時裝造型和表現其實相當搶眼。

整體來說,Once Upon A Time 目前看起來似乎蠻有希望可以撐過至少一季,應該不會像另一個 ABC 的影集,新版的 Charlie's Angels 那樣,第1季播出4集就宣佈確定腰斬(不過其實比起來,還是 NBC 砍影集砍的比較狠)。

繼續閱讀全文