script (⏱,💰)

script (⏱,💰)

NG#7 - 調用數據

ng7 start

本任務內容是關於 StarkNet 合約的 calldata。calldata 是指函數調用時傳遞給函數的數據,在 StarkNet 中,傳入的數據可能是各種類型,但最終都會轉換為多個 felt252 組成的 snapshot,具體見call_contract_syscall

有 2 個合約,PortalSpell 合約中的 cast 接受的是 1 個Array<PortalData>,DrunkenMage 合約中的 cast 是傳入 2 個Array<felt252>。需要你傳入 2 個 Array 構成的 calldata,和Array<PortalData>前 2 個 PortalData 是相同的(驗證見合約代碼)。

struct PortalData {
  location: felt252,
  details: Array<felt252>
}

// 預期的函數簽名
cast(portal_data: Array<PortalData>)

// Mage 的函數簽名
cast(origin: Array<felt252>, destination: Array<felt252>)

上面的 cast 會通過 dispatcher 跨合約調用,無需驗證參數是否一樣。

思路#

首先,先需要了解 StarkNet 合約是怎麼解析 calldata 的,任務的 walkthough 裡說得很清楚了。

編碼的目標是 Array<PortalData>,值是
[ 0: { location: 'TAVERN', details: [ 'OPEN', 'PORTAL' ] }, 1: { location: 'HOME', details: [ 'CLOSE', 'PORTAL' ] } ]

由於實現了 Serde,可以對 struct 進行 serialize,在測試中打印後,單個 struct 是這樣編碼的

[DEBUG] TAVERN          (raw: 0x54415645524e
[DEBUG]                 (raw: 0x2
[DEBUG] OPEN            (raw: 0x4f50454e
[DEBUG] PORTAL          (raw: 0x504f5254414c

2 個 PortalData 加在一起,前面再加個 2,就是最終編碼。

drunk_spell.cast(origin, destination) 傳入是分開的,我們必須找到 origin 和 destination 的值,以便當它被解釋為 Array<PortalData> 時,它反映了所需的 calldata。

需要驗證的只是前 2 個 Portal,是 ['TAVERN', 2, 'OPEN', 'PORTAL', 'HOME', 2, 'CLOSE', 'PORTAL']就行。

如果再加一個PortalData {location: 'VOID', details: ['ANY']},編碼會變成[3, 'TAVERN', 2, 'OPEN', 'PORTAL', 'HOME', 2, 'CLOSE', 'PORTAL','VOID', 1 , 'ANY'],也是可以通過驗證的。

但如果要拆分成 2 個 array 分別傳入,就是 [3, 'TAVERN', 2, 'OPEN', 'PORTAL']['PORTAL', 'HOME', 2, 'CLOSE', 'PORTAL','VOID', 1 , 'ANY'],'PORTAL' 轉為 felt 的十進制數字會非常大,想要加長 details 滿足長度要求,或添加更多的 portal,會消耗很多 gas,在測試時運行會報錯。

那如果繼續增加 Portal 的個數,直到第二個參數 array 的長度是個非常小的數字的話,就不用消耗很多 Gas 了。接下來你應該知道怎麼寫了。

我通過了測試後,準備部署合約去破解。先用 starkli invoke 發現不能傳 array,snforge invoke --calldata可以傳 calldata,但又不知道怎麼傳入帳戶等額外參數。如果部署 hack 合約有點麻煩了。最終發現直接走 voyager 瀏覽器發送兩個 calldata 是最簡單的。

總結#

StarkNet 的 calldata 組成規則很簡單,這題燒腦的地方是需要添加更多 Portal 去組成特定的 calldata,蠻有意思的 CTF 題目。

ng7 end

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。