技術|“狩零人”威脅攻擊分析報告!
事件
近期,降維安全實驗室(johnwick.io)接到白細胞安全社群(whitecell.io)反饋的一起丟幣事件.我們立即與丟幣使用者取得了聯絡,溝通丟幣的過程,又分析了他丟幣時所用的手機系統,可是並沒有發現使用者有洩露私鑰的可能.但是使用者購買的價值幾十萬的數字貨幣又實實在在地瞬間被黑客轉移,且已被兌換成ETH,BTC等主流幣.這背後究竟隱藏了何種玄機?下面就帶大家一起走進科學.
丟幣事件相關交易記錄:
https://etherscan.io/address/0xa9cbada29093adaf1ba685ac4c6b0486a05876c7#tokentxns
分析
以太坊的私鑰
在分析之前,先給大家簡要說明下以太坊的私鑰是什麼.
它是一個64個字元的16進位制數(32位元組),使用者需妥善保管,一旦丟失,也就意味著失去了對以太坊賬戶的控制權. 如果我們已經建立過一個以太坊賬戶,並匯出了其私鑰,那麼即使解除安裝了錢包應用,只需要在別處再次匯入這個私鑰,就可以恢復我們的賬戶.
自作聰明的錢包
但是如果使用者匯入的私鑰不足/超過32位元組,錢包應用應當如何處理呢? 有些錢包應用(如imToken)會直接拒絕這種畸形資料,並提示使用者輸入了無效私鑰.還有些錢包(如下面案例)會在後臺自作主張地幫使用者填0/截斷成32位元組,並成功匯入修改後的私鑰,強行達成共識.
在此次事件涉及到的錢包應用就如此處理使用者輸入資料的.經過技術分析,我們定位到問題出在錢包使用的公開庫keythereum
Buffer.concat
連接合並Buffer.alloc
建立的補齊用全0資料和使用者輸入的私鑰,構成32位元組的"私鑰".
相關程式碼片段及註釋如下:
privateKeyToAddress: function (privateKey) {
var privateKeyBuffer, publicKey;
privateKeyBuffer = this.str2buf(privateKey);
if (privateKeyBuffer.length < 32) { // <-- "私鑰"長度小於32位元組
privateKeyBuffer = Buffer.concat([ // 拼接成32位元組
Buffer.alloc(32 - privateKeyBuffer.length, 0), // 填0 buffer
privateKeyBuffer // 使用者輸入"私鑰"
]);
}
publicKey = secp256k1.publicKeyCreate(privateKeyBuffer, false).slice(1);
return "0x" + keccak256(publicKey).slice(-20).toString("hex");
},
Buffer.concat = function concat (list, length) {
if (!isArray(list)) {
throw new TypeError('"list" argument must be an Array of Buffers')
}
if (list.length === 0) {
return Buffer.alloc(0)
}
var i
if (length === undefined) {
length = 0
for (i = 0; i < list.length; ++i) {
length += list[i].length
}
}
var buffer = Buffer.allocUnsafe(length)
var pos = 0
for (i = 0; i < list.length; ++i) {
var buf = list[i]
if (!Buffer.isBuffer(buf)) {
throw new TypeError('"list" argument must be an Array of Buffers')
}
buf.copy(buffer, pos)
pos += buf.length
}
return buffer
}
Buffer.allocUnsafe = function (size) {
return allocUnsafe(null, size)
}
function allocUnsafe (that, size) {
assertSize(size)
that = createBuffer(that, size < 0 ? 0 : checked(size) | 0)
if (!Buffer.TYPED_ARRAY_SUPPORT) {
for (var i = 0; i < size; ++i) {
that[i] = 0
}
}
return that
}
大意的使用者
回到開頭的丟幣事件,既然使用者沒有洩露私鑰,那他是如何被黑客轉移走所有數字貨幣的呢? 我們在審查使用者整個購買數字貨幣的流程中,發現了一條奇怪的交易, 在(TxHash0xebef7c15fb184be685208a32565848c70dc53495091919b0cc6134db090c3bea)
使用者購買數字貨幣的賬號前置補0後居然是使用者自己賬號的私鑰!!!
具體計算過程如下:
順著這個線索,經過反覆溝通,我們終於還原了客戶被攻擊的場景:
- 客戶在複製自己先前儲存的私鑰過程中,粗心大意,誤將交易所/專案方賬戶地址當作私鑰複製到該款錢包的"匯入私鑰"輸入框
- 該錢包應用沒有對使用者輸入的原始資料做校驗,直接補0,生成了一個賬戶地址.
- 客戶用這個生成的賬戶,先後購買了1,371,196.8173516和214,400.50169127數量的
Distributed Credit Chain(DCC)
代幣. - 監控以太坊主鏈並發現了此問題的黑客隨後將使用者的代幣悉數轉走洗白.
後續
我們想這樣的錯誤絕對不是孤案,於是我們寫了個指令碼爬取了以太坊從創世區塊開始截至目前的所有地址,約5500萬.然後以這些以太坊地址(20位元組)為藍本,前置補0填充構成私鑰(32位元組),並以此匯出公鑰,雜湊出地址. 再拿這些地址在前述的5500萬地址中進行碰撞查詢,初步結果讓人驚訝,至少有125個地址可以由這種模式得出.我們粗略檢查了其中的地址,發現一些賬號中的數字貨幣疑似已被黑客洗走.我們將這些守株待兔收割的黑客稱作**“狩零人”,並將此種攻擊命名為"狩零人"攻擊**.BTW,此類攻擊已加入我們的智子威脅感知系統,使用本系統的合作伙伴可以第一時間獲知預警資訊.
部分碰撞出的地址如下: