xChar

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>
}

// Expected function signature
cast(portal_data: Array<PortalData>)

// Mage's function signature
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

Loading comments...