- 前言
- 區塊鏈和挖礦相關概念
- 挖礦木馬
- 挖礦協議Stratum
- Stratum工作過程
前言
之前做了一個關於“挖礦行為檢測”的大創訓練專案,在這裡記錄一下我關於挖礦檢測相關內容的學習。
區塊鏈和挖礦相關概念
區塊鏈
首先需要了解一些關於區塊鏈的內容。注意,區塊鏈和挖礦是兩個緊密相關但又各自獨立的概念,它們並不是同一件事。
區塊鏈是一種分散式賬本技術,它透過去中心化的方式安全地記錄交易資料。每個區塊包含一組交易記錄,並透過加密的方式與前一個區塊連結起來,形成一個不可篡改的鏈式結構。這種設計使得區塊鏈具有很高的安全性和透明性。
簡單地說,區塊鏈就好比一個公共的賬簿,任何人都可以檢視賬簿中的記錄,但一旦記錄被寫入,就很難更改或刪除。這本賬簿由網路中的許多人共同維護,每個人都持有賬簿的副本。如果想要修改某一條記錄,那麼就要修改所有的人手中賬簿的記錄,這是很難做到的。因此很難對其進行篡改。
區塊鏈的幾個核心特點:去中心化、不可篡改、透明性、共識機制、智慧合約等。
區塊
區塊鏈的一個核心組成部分就是區塊,區塊鏈也可以說是由多個區塊按照時間順序連線形成的鏈式資料結構。
每個區塊通常包含以下內容:
-
區塊頭(Block Header):區塊頭包含了區塊的後設資料:
版本號:表示區塊遵循的協議版本
前一區塊的雜湊值:前一區塊的雜湊值確保了區塊的順序和不可篡改性
Merkle根:一種雜湊樹的根節點,它代表了區塊中所有交易的集合,透過它可以快速驗證區塊中交易的存在性和有效性
時間戳:記錄區塊被建立的時間
難度目標(Target):挖礦難度的表示,用於挖礦過程中的工作量證明
Nonce(隨機數):挖礦過程中使用的一個隨機數,用於生成滿足特定條件的區塊雜湊值 -
交易列表(Transaction List):區塊中包含的交易,它們是區塊的主體部分:
傳送者和接收者的公鑰或地址
交易金額
交易的附加資訊,如交易費用、時間戳等 -
區塊大小:區塊中所有交易資料的總大小,這個大小通常有最大限制,以保證網路的效率
-
區塊雜湊(Block Hash):區塊頭透過雜湊函式加密後得到的雜湊值,代表了整個區塊的獨特標識
訪問這個網站:https://blockchair.com/ 可以瀏覽各類加密貨幣的區塊情況。
比如下圖中的Bitcoin blocks,可以清晰的看到此時最新的一個比特幣塊為842040,這個塊的雜湊值為00000...e11a0,時間戳為2024-05-04 10:30:57UTC,由礦工Foundry USA Pool挖到。
區塊鏈相關概念就瞭解這麼多,接下來看看挖礦是怎麼回事。
挖礦相關術語
-
礦機:礦機是專門用於挖礦的硬體裝置,礦機通常有強大的計算能力。
-
礦池:是一群礦工聯合起來共同挖礦的平臺,透過提供他們的計算能力合作解決問題,然後按貢獻的大小分配獎勵。
-
錢包:在區塊鏈和加密貨幣領域,錢包是用於管理使用者的公有和私有金鑰、傳送和接受數字貨幣、檢視餘額等。
挖礦 是個將待確認的交易資料包含到區塊鏈中,從而完成對這些交易進行確定的分散式共識系統。
挖礦的目的是什麼?
透過挖礦,可以強制性保證塊鏈中的資料按時間順序儲存,保持比特幣網路的中立性,且允許比特幣網路上不同的計算機對系統狀態達成一致。交易要獲得確認,必須要被打包到一個符合非常嚴格的密碼學規則的區塊中,並透過比特幣網路進行驗證。這些規則可以防止對已有塊的修改,因為一旦有改動,之後所有的塊都將失效。挖礦的難度和中彩票相當,沒人可以輕易地、連續地將新塊加入到塊鏈中。因此,沒有集體和個人可以控制塊鏈中包含什麼樣的內容或者替換掉塊鏈中的部分內容以達到撤銷他們交易的目的。
這就是一個達到共識的過程,網路中的節點透過共識機制來驗證和記錄新的交易,常見的共識機制包括工作量證明(PoW)和權益證明(PoS)。
下面這個指令碼大致描述了挖礦的過程:
# 挖礦函式示例工作量證明(PoW)
def mine_block(version, previous_block_hash, merkle_root_hash, time_stamp, difficulty):
'''
計算當前的區塊頭資料的SHA-256雜湊值,改變nonce隨機數,直到計算出小於目標值的hash
version 版本
previous_block_hash 前一個區塊頭的雜湊值
merkle_root_hash 當前塊默克爾樹根的雜湊值,當前區塊所有交易產生的默克爾樹根節點的雜湊值
time_stamp 時間戳
difficulty 當前區塊PoW演算法的難度值
nonce 隨機數
'''
# 根據當前難度值計算出一個目標值(控制在大約10分鐘的計算量)
# 難度值受到網路上的總計算能力的影響,挖礦的難度會根據網路上的總計算能力進行自動調整
target = calculate_target(difficulty)
nonce = 0
while True:
# 構建區塊頭資料
block_header = f"{version}{previous_block_hash}{merkle_root_hash}{time_stamp}{difficulty}{nonce}".encode('utf-8')
# 計算SHA-256雜湊值(兩次)
hash_temp = hashlib.sha256(block_header).hexdigest()
hash_result = hashlib.sha256(hash_temp.encode()).hexdigest()
# 將雜湊值轉換為大整數與target比較,是否達到目標
hash_int = int(hash_result, 16)
if hash_int < target:
print("Nonce:", nonce)
print("Hash:", hash_result)
break
# 隨機數+1(運算在千億次數量級)
nonce += 1
簡單地說,挖礦就是不斷修改區塊頭中的引數,並計算區塊頭的雜湊值,直到其雜湊值與目標難度相匹配的過程。雜湊函式的結果無法提前得知,也沒有能得到一個特定雜湊值的模式,所以這個過程十分消耗計算資源。
聊了這麼多,區塊鏈和挖礦的概念這些都不是重點,大概瞭解就好。重點是惡意的挖礦木馬會對的主機造成嚴重的影響,要透過一些手段把識別這些惡意程式,並做出應急的處置。
挖礦木馬
市面上除了比特幣,還有很多其他加密貨幣。比如以太坊(ETH)、萊特幣(LTC)、門羅幣(XMR)等。
挖礦木馬的常見樣本
-
CoinHive:這是最常見的挖礦病毒之一,它透過JavaScript在使用者的瀏覽器中執行來挖取Monero。
-
XMRig:此病毒通常以開源軟體的形式提供,用於CPU挖礦,專門針對Monero加密貨幣。
-
WannaMine:這是一種基於WannaCry勒索軟體的挖礦病毒。它使用EternalBlue漏洞來傳播,並在受感染的機器上秘密挖礦。
還有非常多各種型別的挖礦木馬,這裡不介紹了。
挖礦木馬控制機器挖礦的方式
-
可執行檔案:儲存在機器上的典型惡意程式,通常透過設定計劃任務或修改登錄檔項實現持久化,長期進行加密貨幣的挖礦作業。
-
基於瀏覽器的挖礦木馬:使用JavaScript(或類似技術)的挖礦木馬是在瀏覽器中執行。只要瀏覽器開啟被植入挖礦木馬的網站,就會執行挖礦執行,持續消耗資源。
-
無檔案挖礦木馬:利用如PowerShell等合法工具在機器的記憶體中執行挖礦作業,具有不落地、難檢測等特點。
挖礦木馬行為特徵
挖礦木馬顯著的行為特徵就是極大的佔用CPU及GPU資源。主要包括:CPU和GPU使用率高、響應速度慢、 崩潰或頻繁重新啟動、系統過熱、異常網路活動。其次是在網路流量中,挖礦木馬通訊過程採用專門的通訊協議,因此存在一定的網路通訊特徵。
檢測方法
-
方法1 網路側檢測:通訊內容檢測和礦池地址域名請求DNS請求歷史記錄檢測。
-
方法2 主機檢測:對應程序CPU使用率長時間居高不下,部分挖礦木馬採用多方式隱藏程序,且具備多種持久化駐留方式。
我們採用的是基於網路側檢測的方案,主要是分析挖礦資料包中的一些特徵值進行檢測。
挖礦協議Stratum
Stratum協議在2012年被提出,目的是擴充套件對礦池挖礦的支援,並取代了舊的getwork協議。Stratum協議允許礦機透過TCP連線與礦池使用 JSON-RPC 2.0 訊息編碼進行通訊,從而接收挖礦任務並提交工作證明。
Stratum工作過程
1. 礦機連線伺服器
礦機使用 mining.subscribe 方法連線礦池,訂閱當前連線。
{
"id": 1,
"method": "mining.subscribe",
"params": []
}
礦池返回相關資訊,需要礦工記錄在本地。
{
"id": 1,
"result": [
[
"mining.set_difficulty",
"subscription id 1"
],
[
"mining.notify",
"subscription id 2"
]
],
"08000002": 4,
"error": null
}
mining.set_difficulty:用於礦池去設定礦機當前提交任務的最大難度值。
mining.notify:用於礦池向礦機傳送新的挖礦任務。
extranonce1:十六進位制編碼,每個連線唯一的字串,稍後將用於建立生成的事務。
extranonce2_size:指定在挖礦過程中使用的額外隨機數(extranonce2)的大小。
在上面這個示例 "08000002": 4
其中 08000002 表示extranonce1的值,4 表示extranonce2的長度為4位元組。
2. 礦工認證
礦機使用 mining.authorize 方法向礦池提供登入憑證,礦池根據提供的資訊驗證礦工的許可權。
{
"id": 2,
"params": [
"miner",
"password"
],
"method": “mining.authorize”
}
礦池返回授權。
{
"error": null,
"id": 2,
"result": true
}
注意這裡的這個id號是用來區礦工和礦池的應答訊息的,並不是礦工的id。因為礦池的應答訊息結構有些很類似,礦機就是透過訊息的id來區別這些訊息是對應哪個傳送訊息的應答。
還有透過 jsonrpc 方式登入的,下面會提到。
3. 礦池向礦機下發挖礦工作任務
當礦池在被訂閱之後,會使用 mining.notify 方法給礦機傳送最少一個工作任務。
{
"params": [
"bf",
"4d16b6f85af6e2198f44ae2a6de67f78487ae5611b77c6c0440b921e00000000",
"01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff20020862062f503253482f04b8864e5008",
"072f736c7573682f000000000100f2052a010000001976a914d23fcdf86f7e756a64a7a9688ef9903327048ed988ac00000000",
[],
"00000002",
"1c2ac4af",
"504e86b9",
false
],
"id": null,
"method": "mining.notify"
}
按順序描述 params 中每個欄位:
-
job_id:任務號id,每一次任務都有唯一的識別符號
-
prevhash:前一個區塊的hash值
-
coinb1:coinbase幣基交易的最初部分
-
coinb2:coinbase幣基交易的最後部分
-
merkle_branch:交易列表(hash),用於計算merkle根
-
version:區塊版本號
-
nbits:當前區塊難度,也是當前每個礦池全網的難度
-
ntime:當前的時間戳
-
clean_jobs:如果為true,礦工應該退出當前工作,立即開始新的工作,如果為false,礦工仍然可以繼續當前的挖礦工作
4. 礦機開始挖礦
- 構建 coinbase 資訊
為什麼需要構造coinbase資訊?
coinbase交易的存在是網路共識演算法的一部分,coinbase交易是每個新區塊中的第一個交易,它確認了區塊的建立和挖礦過程的成功。在挖礦過程中,礦工需要找到一個特定的雜湊值,就修改coinbase交易中的Nonce欄位並重新計算區塊雜湊。
總之,構造coinbase資訊對於挖礦過程至關重要,它不僅是礦工獲得獎勵的手段,也是維護區塊鏈安全性和完整性的關鍵環節。
構建coinbase資訊要用到的資料:coinb1,extranonce1,extranonce2_size,coinb2
在上面的互動過程中已知extranonce2_size,所以能夠產生有效的extranonce2。構造coinbase只需要把這幾個部分拼接起來:
coinbase = coinb1 + extranonce1 + extranonce2 + coinb2
當然這個conbase還需要進行2次hash執行,投入後面的使用:
# python指令碼示例
import hashlib
import binascii
coinbase_hash_bin = hashlib.sha256(hashlib.sha256(binascii.unhexlify(coinbase)).digest()).digest()
- 計算 Merkle 根
基本的coinbase構建完畢了,現在就需要把區塊中的其他交易給聯絡起來,需要計算一個merkleroot。
為什麼是 Merkle 根?
Merkle樹(也稱為二叉雜湊樹)是一種資料結構,從區塊內所有交易的雜湊值開始構建,將這些雜湊值配對,然後計算每個配對的雜湊值,形成第二層。這個過程遞迴地重複,直到生成單個雜湊值,即Merkle根(Merkle Root)。Merkle樹允許透過Merkle證明快速驗證區塊中單個交易的存在性,任何對樹中資料的更改都會導致Merkle根的變化。
所以說只要Merkle根沒變,區塊中的交易就沒變。想要證明某個交易是區塊的一部分時,就只需要提供Merkle根,無需提供整個區塊的資料,這大大提高了效率。
構建Merkle根,利用 coinbase 和 merkle_branch 兩兩配對,然後把配對完成後進行2次hash運算,得到上一層的結果,然後重複上面的過程,繼續配對計算,最終得到Merkle根。
# python指令碼示例
import binascii
def build_merkle_root(self, merkle_branch, coinbase_hash_bin):
merkle_root = coinbase_hash_bin
for h in self.merkle_branch:
merkle_root = doublesha(merkle_root + binascii.unhexlify(h))
return binascii.hexlify(merkle_root)
- 構建區塊頭
填充餘下的5個欄位:
block_header = version + prevhash + merkle_root_hash + ntime + nbits + nonce
5. 礦機向礦池提交工作量
當礦工發現符合請求難度的任務,會使用 mining.submit 方法提交給礦池這樣的share:
{
"params": [
"miner",
"bf",
"00000001",
"504e86ed",
"b2957c02"
],
"id": 4,
"method": "mining.submit"
}
按順序描述 params 中每個欄位:
-
worker_name:之前認證過的賬號
-
job_id:任務號id
-
extranonce2:隨機數extranonce2的值
-
ntime:時間戳
-
nonce:隨機值
礦池拿到以上5個欄位後,首先根據任務號id找出之前分配任務前儲存的資訊,然後重構區塊,再驗證share難度,對於符合難度要求的share,在檢測是否符合全網難度。
預設share難度是1(難度1的目標是0x00000000ffff0000000000000000000000000000000000000000000000000000),礦池偶爾可以要求礦工更改share難度:
{
"id": null,
"method": "mining.set_difficulty",
"params": [2]
}
這意味著收到伺服器工作任務的礦工下一條任務講改為難度2,這個數字的意思是派給礦機的任務難度是1個單位難度值的2倍。
關於更多Stratum的內容參考:https://en.bitcoin.it/wiki/Stratum_mining_protocol#mining.subscribe